functional programming and default parameters

Ralf Muschall ralf.muschall at alphasat.de
Fri Jun 29 12:09:21 EDT 2001


"Nick Perkins" <nperkins7 at home.com> writes:

> >>> fns = [lambda:i for i in range(3)]
> >>> fns[0]()
> 2
> >>> fns[1]()
> 2

> but this does work:

> >>> fns = [lambda i=i:i for i in range(3)]
> >>> fns[0]()
> 0
> >>> fns[1]()
> 1

> from __future__ import nested_scopes
> def fnlist(n):
>     list =[]
>     for i in range(n):
>         def fn():       #or: def fn(i=i)
>             return i

(The easy part of) this problem is very old and also bites newbies in
Lisp.  The variable "i" is created only once (at the top of the loop),
the only thing that changes later is the value.  As a result, all
closures share the same thing "i" (you can check this by creating
functions gn(j) which make i=j -- changing the "i" once causes all
"fn"s to return the new value).

The solution is to create a fresh variable *in* the loop and use
this for the closure.

In CL, a common idiom is this:

  (let ((fns (make-array (list 3)))) ; bad
    (do ((i 0 (1+ i)))
	((> i 2))
      (setf (aref fns i) (lambda () i))) ; shared i
    fns)

wrt.

  (let ((fns (make-array (list 3)))) ; good
    (do ((i 0 (1+ i)))
	((> i 2))
      (let ((j i))
	(setf (aref fns i) (lambda () j)))) ; fresh j
    fns)

The top example makes an array of three functions, each one returning
the value of the common "i" (which is 3 after the loop ended).

The bottom example does the right thing.  Of course, real programmers
would write (let ((i i)) ...)

Unfortunately, in Python writing

for i in range(3):
  j=i
  fns[i]()=lambda:j

fails, and even this fails too:

for i in range(3):
  j=i+10
  a[i]=lambda:j

map(x:x(),fns) gives [12,12,12].

My guess is that the interpreter detects the *creation* of the
variable "j" in the loop and optimizes by shifting that in front of
the loop, leaving only the *assignment* in the loop body.  (I have no
idea how this is supposed to happen in interpreted code - maybe it
detects the creation during the first loop body traversal, or it
starts interpreting only after the complete loop has been typed in.)

Probably one needs to play nasty tricks against the optimizer (as
Tim's solution does) in order to enforce the creation of a fresh
variable during the loop execution.

Ralf



More information about the Python-list mailing list