Re: [Python-ideas] Ruby-style Blocks in Python Idea [with examples]

Dear all, Here's another stab at making a case for it. I'll avoid referring to Ruby this time round -- I was merely using it as an example of where this approach has been successful. Believe me, there's no Ruby-envy here ;p The motivation: 1. Having to name a one-off function adds additional cognitive overload to a developer. It doesn't make the code any cleaner and by taking away the burden, we'd have happier developers and cleaner code. 2. This approach is more descriptive and in line with the code flow. With blocks the first line says "I'm about to define a function for use with X" instead of the existing way which says "I'm defining a function. Now I'm using that function with X." 3. DSLs -- whether we like them or not, they are in mainstream use. Python already has beautiful syntax. We should be leveraging that for DSLs instead of forcing framework developers to create their own ugly/buggy mini-DSLs. This will enable that. The proposed syntax: using EXPR do PARAM_LIST: FUNCTION_BODY Examples: # Django/App Engine Query Frameworks like Django or App Engine define DSLs to enable easy querying of datastores by users. Wouldn't it better if this could be done in pure Python syntax? Compare the current Django: q = Entry.objects.filter(headline__startswith="What").filter(pub_date__lte=datetime.now()) with a hypothetical: using Entry.filter do (entry): if entry.headline.startswith('What') and entry.pub_date <= datetime.now(): return entry Wouldn't the latter be easier for a developer to read/maintain? Let's compare this App Engine: composer = "Lennon, John" query = GqlQuery("SELECT * FROM Song WHERE composer = :1", composer) with: composer = "Lennon, John" using Song.query do (item): if item.composer == composer: return item Again, being able to do it in Python syntax will save developers the hassles of having to learn non-Python DSLs. # Event-driven Programming Right now, event-driven programming like it's done in Twisted is rather painful for many developers. It's filled with callbacks and the order in which code is written is completely inverted as far as the average developer is concerned. Let's take Eventlet -- a nice coroutines-based networking library in Python. Their example webcrawler.py currently does: urls = ["http://www.google.com/intl/en_ALL/images/logo.gif", "http://us.i1.yimg.com/us.yimg.com/i/ww/beta/y3.gif"] def fetch(url): print "%s fetching %s" % (time.asctime(), url) httpc.get(url) print "%s fetched %s" % (time.asctime(), url) pool = coros.CoroutinePool(max_size=4) waiters = [] for url in urls: waiters.append(pool.execute(fetch, url)) Wouldn't it be nicer to do this instead: pool = coros.CoroutinePool(max_size=4) for url in urls: using pool.execute do: print "%s fetching %s" % (time.asctime(), url) httpc.get(url) print "%s fetched %s" % (time.asctime(), url) I'd argue that it is -- but then I have bias =) # SCons SCons is a make-esque build tool. In the SConstruct (makefile) for Google Chrome, we find: def WantSystemLib(env, lib): if lib not in env['all_system_libs']: env['all_system_libs'].append(lib) return (lib in env['req_system_libs']) root_env.AddMethod(WantSystemLib, "WantSystemLib") Which we could hypothetically do as: with root_env.WantSystemLib do (env, lib): if lib not in env['all_system_libs']: env['all_system_libs'].append(lib) return (lib in env['req_system_libs']) As someone who's used both make and SCons, I found SCons terribly verbose and painful to use. By using the proposed do statement, SCons could be made extremely pleasant! # Webapp Configuration Configuration in web applications is generally a real pain: application = webapp([('/profile', ProfileHandler), ('/', MainHandler)], debug=True) run(application) Compare to: using webapp.runner do (config, routes): routes['/profiles'] = ProfileHandler routes['/'] = MainHandler config.debug = True I think the latter is more readable and maintainable. Please let me know if more examples would help... I really do believe that a block syntax would make developers more productive and lead to cleaner code. -- love, tav plex:espians/tav | tav@espians.com | +44 (0) 7809 569 369 http://tav.espians.com | http://twitter.com/tav | skype:tavespian

On Mon, Mar 9, 2009 at 11:47 AM, tav <tav@espians.com> wrote:
I showed an example using "def _(...)".
Marginal. The decorator name could clarify this too.
This is quite the non-sequitur. Given that what you propose is trivially done using a decorator, what's ugly/buggy about the existing approach?
Hmm... where does the 'return' return to? The "current Django" has an assignment to q. How do you set q in this example?
Wouldn't the latter be easier for a developer to read/maintain?
But it's not the same. If they had wanted you to be able to write that they could easily have provided an iterator (and actually of course they do -- it's the iterator over all records). But the (admittedly awkward) current syntax *executes the query in the database engine*. There's no way (without proposing a lot of other changes to Python anyway) to translate the Python code in the body of your example to SQL, because Python (the language, anyway -- not all implementations support access to the bytecode) doesn't let you recover the source code of a block at run time.
Again, it's broken the same way.
It's also broken -- unless you also have some kind of alternative semantics in mind that does *not* map to existing Python functions and scopes, all callbacks will reference the last url in the list. Compare this classic stumbling block: addN = [(lambda x: x+i) for i in range(10)] add1 = addN[1] print add1(10) # prints 19
That's just a matter of API design. They could easily have provided a decorator to register the callback. The name of the decorated function could even serve as the key.
Quite the exaggeration.
Again, there's no need to add new syntax if you wanted to make this API easier on the eyes.
Please let me know if more examples would help...
Well, examples that (a) aren't broken and (b) aren't trivially written using decorators might help...
I really do believe that a block syntax would make developers more productive and lead to cleaner code.
I got that. :-)
-- --Guido van Rossum (home page: http://www.python.org/~guido/)

Again, being able to do it in Python syntax will save developers the hassles of having to learn non-Python DSLs.
Hmm. For your GqlQuery example, the way I see it doing it in Python syntax will save the hassle of GqlQuery optimizing the database access. The truth is that you don't know what happens to the query string but you do know that it can't take your function apart and have the database process it. So this example is useless. I also can't tell if using/do is supposed to be a loop or not. Some of your examples are loopy and some aren't. And besides that your examples are apples and oranges: composer = "Lennon, John" query = GqlQuery("SELECT * FROM Song WHERE composer = :1", composer) with: composer = "Lennon, John" using Song.query do (item): if item.composer == composer: return item The first one produces a result: something bound to a variable named query. What does the second one produce? I see no advantage in a new syntax that is this confusing. --- Bruce

tav wrote:
How is this any better than [entry for entry in Entry.filter if entry.headline.startswith('What') and entry.pub_date <= datetime.now()] But note that neither of these is an adequate replacement for the Django expression, because Django can generate an SQL query incorporating the filter criteria. Neither a list comprehension nor the proposed using-statement are capable of doing that. The same thing applies to the App Engine example, or any other relational database wrapper. By the way, having the 'return' in there doing something other than return from the function containing the 'using' statement would be confusing and inconsistent with the rest of the language.
# Event-driven Programming
I've been exploring this a bit myself in relation to my yield-from proposal, and doing this sort of thing using generators is a much better idea, I think, especially given something like a yield-from construct. -- Greg

Apart from the fact that I don't think blocks are needed for any of the above, I feel compelled to poke a hole in one of your examples: On Mon, Mar 9, 2009 at 2:47 PM, tav <tav@espians.com> wrote:
Probably, but it doesn't matter, since Django lazily evaluates chained querys, and doesn't actually evaluate anything until you tell it you want elements. I don't believe there's any way to do this with blocks. -- Cheers, Leif

On Mon, Mar 9, 2009 at 11:47 AM, tav <tav@espians.com> wrote:
I showed an example using "def _(...)".
Marginal. The decorator name could clarify this too.
This is quite the non-sequitur. Given that what you propose is trivially done using a decorator, what's ugly/buggy about the existing approach?
Hmm... where does the 'return' return to? The "current Django" has an assignment to q. How do you set q in this example?
Wouldn't the latter be easier for a developer to read/maintain?
But it's not the same. If they had wanted you to be able to write that they could easily have provided an iterator (and actually of course they do -- it's the iterator over all records). But the (admittedly awkward) current syntax *executes the query in the database engine*. There's no way (without proposing a lot of other changes to Python anyway) to translate the Python code in the body of your example to SQL, because Python (the language, anyway -- not all implementations support access to the bytecode) doesn't let you recover the source code of a block at run time.
Again, it's broken the same way.
It's also broken -- unless you also have some kind of alternative semantics in mind that does *not* map to existing Python functions and scopes, all callbacks will reference the last url in the list. Compare this classic stumbling block: addN = [(lambda x: x+i) for i in range(10)] add1 = addN[1] print add1(10) # prints 19
That's just a matter of API design. They could easily have provided a decorator to register the callback. The name of the decorated function could even serve as the key.
Quite the exaggeration.
Again, there's no need to add new syntax if you wanted to make this API easier on the eyes.
Please let me know if more examples would help...
Well, examples that (a) aren't broken and (b) aren't trivially written using decorators might help...
I really do believe that a block syntax would make developers more productive and lead to cleaner code.
I got that. :-)
-- --Guido van Rossum (home page: http://www.python.org/~guido/)

Again, being able to do it in Python syntax will save developers the hassles of having to learn non-Python DSLs.
Hmm. For your GqlQuery example, the way I see it doing it in Python syntax will save the hassle of GqlQuery optimizing the database access. The truth is that you don't know what happens to the query string but you do know that it can't take your function apart and have the database process it. So this example is useless. I also can't tell if using/do is supposed to be a loop or not. Some of your examples are loopy and some aren't. And besides that your examples are apples and oranges: composer = "Lennon, John" query = GqlQuery("SELECT * FROM Song WHERE composer = :1", composer) with: composer = "Lennon, John" using Song.query do (item): if item.composer == composer: return item The first one produces a result: something bound to a variable named query. What does the second one produce? I see no advantage in a new syntax that is this confusing. --- Bruce

tav wrote:
How is this any better than [entry for entry in Entry.filter if entry.headline.startswith('What') and entry.pub_date <= datetime.now()] But note that neither of these is an adequate replacement for the Django expression, because Django can generate an SQL query incorporating the filter criteria. Neither a list comprehension nor the proposed using-statement are capable of doing that. The same thing applies to the App Engine example, or any other relational database wrapper. By the way, having the 'return' in there doing something other than return from the function containing the 'using' statement would be confusing and inconsistent with the rest of the language.
# Event-driven Programming
I've been exploring this a bit myself in relation to my yield-from proposal, and doing this sort of thing using generators is a much better idea, I think, especially given something like a yield-from construct. -- Greg

Apart from the fact that I don't think blocks are needed for any of the above, I feel compelled to poke a hole in one of your examples: On Mon, Mar 9, 2009 at 2:47 PM, tav <tav@espians.com> wrote:
Probably, but it doesn't matter, since Django lazily evaluates chained querys, and doesn't actually evaluate anything until you tell it you want elements. I don't believe there's any way to do this with blocks. -- Cheers, Leif
participants (5)
-
Bruce Leban
-
Greg Ewing
-
Guido van Rossum
-
Leif Walsh
-
tav