A critic of Guido's blog on Python's lambda

Joe Marshall eval.apply at gmail.com
Wed May 10 19:04:53 CEST 2006

Alex Martelli wrote:
> Joe Marshall <eval.apply at gmail.com> wrote:
>    ...
> > The problem is that a `name' is a mapping from a symbolic identifier to
> > an object and that this mapping must either be global (with the
> > attendant name collision issues) or within a context (with the
> > attendant question of `in which context').
> Why is that a problem?  Even for so-called "global" names, Python
> supports a structured, hierarchical namespace, so there can never be any
> collision betwen the "globals" of distinct modules (including modules
> which happen to have the same name but live in distinct packages or
> subpackages) -- I did mention that names could usefully be displayed in
> some strcutured form such as apackage.somemodule.thefunction but perhaps
> I was too tangential about it;-).

Can you refer to inner functions from the global context?  Suppose I
have this Python code:

def make_adder(x):
    def adder_func(y):
        sum = x + y
        return sum
    return adder_func

Can I refer to the inner adder_func in any meaningful way?

> > Matthias Felleisen once suggested that *every* internal function should
> > be named.  I just said `continuations'.  He immediately amended his
> > statement with `except those'.
> If I used continuations (I assume you mean in the call/cc sense rather
> than some in which I'm not familiar?) I might feel the same way, or not,
> but I don't (alas), so I can't really argue the point either way for
> lack of real-world experience.

I meant continuations as in the receiver function in
continuation-passing-style.  If you have a function that has to act
differently in response to certain conditions, and you want to
parameterize the behavior, then one possibility is to pass one or more
thunks to the function in addition to the normal arguments.  The
function acts by selecting and invoking one of the thunks.  A classic
example is table lookup.  It is often the case you wish to proceed
differently depending upon whether a key exists in a table or not.
There are several ways to provide this functionality.  One is to have a
separate `key-exists?' predicate.  Another is to have a special return
value for `key not found'.  Another is to throw an exception when a key
is not found.  There are obvious advantages and drawbacks to all of
these methods.  By using continuation-passing-style, we can
parameterize how the table lookup proceeds once it determines whether
or not the key is found.  We have the lookup procedure take two thunks
in addition to the key.  If the key is found, the first thunk is
invoked on the associated value.  If the key is not found, the second
thunk is invoked.  We can subsume all the previous behaviors:

(define (key-exists? key table)
  (lookup key table
     (lambda (value) #t) ;; if found, ignore value, return true
     (lambda () #f)))  ;; if not found, return false.

(define (option1 key table)
  (lookup key table
    (lambda (value) value)
    (lambda () 'key-not-found)))

(define (option2 key table)
  (lookup key table
    (lambda (value) value)
    (lambda () (raise 'key-not-found-exception))))

(define (option3 key table default-value)
  (lookup key table
    (lambda (value) value)
    (lambda () default-value)))

The unnamed functions act in this regard much like a `local label'.  We
wrap two chunks of code in a lambda and the lookup function `jumps' to
the appropriate chunk.  (If the compiler knows about thunks, the
generated assembly code really will have local labels and jump
instructions.  It can be quite efficient.)

This may look odd and cumbersome, but with a little practice the
lambdas fade into the background and it becomes easy to read.

My point with Matthias, however, was that defining all these
continuations (the thunks) as named internal functions was not only
cumbersome, but it obscured the control flow.  Notice:

(define (named-option3 key table default-value)
  (define (if-found value)
  (define (if-not-found)
  (lookup key table if-found if-not-found))

When we enter the function, we skip down to the bottom (past the
internal definitions) to run lookup, which transfers control to a
function defined earlier in the code.

There are many reasons to avoid this style in Python, so this probably
won't win you over, but my point is that there are times where
anonymous functions have an advantage over the named alternative and
that disallowing anonymous functions can be as cumbersome as
disallowing anonymous integers.

More information about the Python-list mailing list