Theoretical question about Lambda

Paul Foley see at below
Mon May 6 21:34:13 EDT 2002


On Mon, 06 May 2002 15:15:59 GMT, Alex Martelli wrote:

> Paul Foley wrote:
>         ...
>> OK; question: what's the difference, if any, between
>> 
>>    x = n
>>    do_something
>> 
>> and
>> 
>>    def foo(x):
>>       do_something
>> 
>>    foo(n)
>> 
>> as far as x is concerned?

> Assuming these snippets are top-level code, "the difference as
> far as x is concerned" is clearly that x is a global name in the
> first snippet, and a local name of foo in the second snippet.

And if they're not at top level?  The scope difference isn't really
what I was getting at.

The point is that, in the case of something like

  def foo(x):
     bar
     x = 7
     baz

there's only _one_ binding for x: doing "x = 7" just changes the value
in the preexisting binding.  That's why

   def foo(x):
      a = lambda: x
      x = 7
      b = lambda: x
      return a,b

returns two functions that both return 7; if there was a new binding
after the "x = 7", the functions would return different values
[assuming you don't call foo(7), of course.  Also assuming nested_scopes]


In Lisp, you can do both:

  (defun foo (x &aux a b)
    (setq a (lambda () x))
    (setq x 7)                 ; assignment
    (setq b (lambda () x))
    (list a b))

vs.

  (defun foo (x &aux a b)
    (setq a (lambda () x))
    (let ((x 7))               ; binding
      (setq b (lambda () x)))
    (list a b))


Note that

   (let ((a1 b1) (a2 b2)) ...)

is essentially syntactic sugar for

   ((lambda (a1 a2) ...) b1 b2)

Lambda is how you do binding; setq can only assign into an existing
binding.  Same with Python's "=", except that the compiler notices any
assignments and automatically adds "&aux var" to the lambda list (to
put it in Lisp terms) -- and /that's/ what does the binding, not the
"=", which you can easily demonstrate:

   def test():
      print x
      x = 42

raises an error because the (empty, error-causing) binding for x is
already in force when the "print" statement executes, before the "="
is even reached.

>> Given that (lambda: x) /does/ close over x with nested scopes enabled,
>> can you explain why
>> 
>>    fns = getfns(seq)
>> 
>> produces a list of functions that all return seq[-1]?  And why, as you

> Because "lambda: x" is a callable that returns whatever object name x is 
> bound to at the time it's called (rather than, at the time it's created).

Yes, but it captures the binding that's in effect at the time the
function is created (i.e., when the lambda form is evaluated, not when
it's eventually called).  That's what "closure" means.

And that's what I'm getting at: yes, when you call the function you
get whatever value is in that binding at the time of the call -- the
fact that all five functions return the same value shows that they all
share the same binding!

>>   def getfns2(seq):
>>      return [(lambda x=x: x) for x in seq]
>> 
>> produces functions that return all the elements of seq?
>> [Assume you call them with no arguments!]

> Because "lambda x=x: x" is a callable that (when called with no arguments)
> returns whatever object name x was bound to at the time the callable was 
> created (rather than, at the time it's called).

Hmmm.  Does it make a difference if I write (lambda q=x: q)?  It still
returns whatever value the variable (now named q) is bound to at the
time of the call (/not/ when it was created!), but now you have a new
binding, not shared by the other functions; the value in that new
binding is the value of x at the time the binding was created [and you
can't change it, in Python, but that's not relevant]

Of course, you could do

   def getfns2(seq):
      return [(lambda x: lambda: x)(x) for x in seq]

to get the same effect without the unwanted optional argument.
[Again, assuming nested_scopes is enabled]


What I'm saying is that when you explain Python to people by drawing
boxes for variables with arrows pointing at their values, as in the
recent thread, the binding is the box, or the box-and-arrow pair, not
the arrow.  Assignment is making an arrow point somewhere else;
binding is making a new box, with its own arrow.  [As long as you
don't have dynamic variables, anyway; but Python doesn't]

-- 
Nonono, while we're making wild conjectures about the behavior of
completely irrelevant tasks, we must not also make serious mistakes,
or the data might suddenly become statistically valid.
                                                           -- Erik Naggum
(setq reply-to
  (concatenate 'string "Paul Foley " "<mycroft" '(#\@) "actrix.gen.nz>"))



More information about the Python-list mailing list