A friendlier, sugarier lambda -- a proposal for Ruby-like blocks in python
James Stroud
jstroud at mbi.ucla.edu
Sat Oct 14 05:00:53 EDT 2006
brenocon at gmail.com wrote:
> Hi all --
>
> Compared to the Python I know and love, Ruby isn't quite the same.
> However, it has at least one terrific feature: "blocks". Whereas in
> Python a
> "block" is just several lines of locally-scoped-together code, in Ruby
> a
> "block" defines a closure (anonymous function). To avoid confusion
> let's call
> them Ruby block-closures. I see them as just a syntax for defining
> closures
> and passing them into method calls. I think something analogous could
> be added
> to Python in a very simple manner that would make closures much more
> readable
> and usable, and nail some use cases nicely.
>
> To define a new closure and pass it into a function call, there are two
> current
> methods: inline 'def' and 'lambda'. Consider the following Twisted-ish
> code:
>
> deferred = fetchPage('http://python.org')
> def _showResponse(response)
> print "fancy formatting: %s" % response.text
> deferred.addCallback(_showResponse)
>
> Lots of Twisted code has to be written backwards like this.
> Theoretically, it
> might be nice to use lambda right in the addCallback() call, like:
>
> deferred.addCallback(lambda r: print("fancy formatting %s"
> %r.text) )
>
> But this is awkward since the lambda is constrained to be one line; you
> can't
> come back later and add much to the callback's code. Furthermore, this
> example
> isn't even legal, because 'print' isn't a function, but a statement --
> lambda
> is further constrained to only contain an expression.
>
> Many have complained about this crippled-ness of lambda, but it
> actually makes
> some sense. Since Python uses colons and indentation to define blocks
> of code,
> it would be awkward to close a multiline lambda. The best I could
> think of
> would look like
>
> deferred.addCallback(lambda r:
> print("fancy formatting %s" % r.text)
> )
>
> ^
> |
>
> That trailing paranthesis is WAY un-Pythonic. We don't close code
> blocks like
> that! And in general, declaring big multiline anonymous functions in
> the
> middle of a list of normal variable arguments is weird -- it just
> doesn't fit.
> It's perfectly legal to pass in 4 closures, interspersed with number
> and string
> arguments. Imagine defining all of those inline with 'lambda'
> expressions!
> And what about nesting? And then there's the term "lambda", while a
> great
> homage to Lisp and computation theory, just isn't the friendliest
> programming
> vocab term.
>
> (from my limited understanding,) Ruby block-closures assume a specific
> use
> case: You want to pass exactly one multiline, defined-right-there
> closure to a
> method when calling it. Therefore, you should get to define the
> closure
> *immediately following* the method call. I suggest a Python version
> with a
> keyword 'using' (or 'with'?) that takes the block of code as a closure,
> and
> passes it to the method call as the *last argument*. The above example
> becomes:
>
> deferred.addCallback() using response:
> print "fancy formatting %s" % response.text
>
> and in general, the following two code snippets are equivalent:
>
>
> def _f(x,y):
> [do stuff with x and y]
> function_with_callback(a,b,c, _f)
>
> function_with_callback(a,b,c) using x,y:
> [do stuff with x and y]
> next_statement()
>
> ... where function_with_callback wants a 2-arg function as its last
> argument.
> It gets to call _f, or equivalently the defined-right-there
> closure/ruby-block,
> however it wants to -- wait for an I/O operation to finish, whatever.
> I'm not
> so hot about the fact that it looks like addCallback() should be
> completed
> before the 'using' keyword kicks in, but this is the simplest I could
> think of.
>
> This syntax does not let you define a new function and store it as a
> local
> variable. Python already has inline 'def' for that (that is, you can
> do a
> 'def' in any block of code you want, and it stays local to that scope.)
> It
> does not, strictly speaking, let you do anything new -- as Guido has
> stated,
> you could ditch lambda and achieve the equivalent by declaring the
> little
> callback function as an inline 'def', like in the first deferred
> example here.
>
> This only optimizes for the case of defining a closure only for the
> purpose of
> passing it in as an argument to a method. However, this must be the
> only use
> for anonymous functions in Python, since we already have inline 'def'.
> I've
> always felt that passing in a lambda in lisp and python, or the
> equivalent
> anonymous function(x,y) {} in javascript, or anonymous classes in java,
> was
> always awkward. You have to think about defining a new anonymous
> function
> *within* your method call and such, and then you have to close
> parantheses
> after it -- it's just weird, I dunno.
>
> This proposal could also handle some of the use cases mentioned in PEP
> 310 and
> 340. If you design your callback-taking functions to have only one
> callback
> and have it as the last argument, you can trivially write lock
> acquition (omit
> 'using' for a no-arg block-closure):
>
> def protect(lock, f):
> lock.acquire()
> f()
> lock.release()
>
> protect(myLock):
> [do stuff that needs myLock to be acquired]
>
> Of course, the definition of protect() might have try/finally wrapped
> around
> the f() call. (Interestingly, this starts looking like a way to define
> new
> control-like structures. I haven't thought through the implications.)
>
> ActiveRecord, Rails' object-relational mapper, does almost exactly this
> for
> database transactions, and I have found it quite nice:
>
> # User is a sqlobject/sqlalchemy/django/whatever ORM class;
> # User.transaction is a class method executing its passed-in
> closure within
> # the user table's START TRANSACTION and STOP TRANSACTION.
>
> user1, user2 = getTwoUsers()
> User.transaction() using user1, user2:
> someRaceConditionProneStuff(user1, user2)
> moreRaceConditionProneStuff(user1, user2)
>
> There might be some sort of overlap with PEP 343 and the 'with'
> statement, but
> I'm not sure exactly. Sorry I'm late to the game and commenting on
> last year's
> PEP's, but I've only started reading them. Note that PEP's 343 and 340
> are
> very focused on resource management -- but I think that letting one
> define code
> blocks as closures could make resource handling routines be easily
> written in
> Python. Furthermore, tons more stuff -- like Deferreds and such --
> could be
> added. (Ruby uses block-closures to do really basic constructs such as
> foreach
> iteration. Python does a fine job with "for x in L" and list and
> generator
> comprehensions... enough so that map/lambda is obsolete! I'm just
> trying to
> see if there are use cases in Python for block-closures.)
>
> I've been trying to search for similar proposals but have come up dry.
> Anything like this out there? I hear Ruby blocks are heavily inspired
> by
> Smalltalk; anyone know more?
>
> Is it feasible to assume the primary use of closures is as part of an
> argument
> list, and such argument lists will want only one argument that is a
> closure?
> Does doing so avoid any big annoyances of functional programming?
>
> Is this completely, totally incompatible with the current state of
> Python and
> previous deliberations :) ? e.g. I haven't thought much about how this
> would
> interact with yield and generators.
>
> But really, I'm just idly curious -- does anyone think this might be
> useful?
>
>
> Take care,
> Brendan
>
http://mail.python.org/pipermail/python-list/2004-April/215805.html
More information about the Python-list
mailing list