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

Guido van Rossum guido at python.org
Mon Mar 9 20:10:11 CET 2009


On Mon, Mar 9, 2009 at 11:47 AM, tav <tav at espians.com> wrote:
> 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.

I showed an example using "def _(...)".

> 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."

Marginal. The decorator name could clarify this too.

> 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.

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?

> 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

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.

> 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.

Again, it's broken the same way.

> # 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 =)

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

> # 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):
s/with/using/
>      if lib not in env['all_system_libs']:
>          env['all_system_libs'].append(lib)
>      return (lib in env['req_system_libs'])

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.

> 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!

Quite the exaggeration.

> # 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.

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. :-)

> --
> love, tav
>
> plex:espians/tav | tav at espians.com | +44 (0) 7809 569 369
> http://tav.espians.com | http://twitter.com/tav | skype:tavespian
>



-- 
--Guido van Rossum (home page: http://www.python.org/~guido/)



More information about the Python-ideas mailing list