Lisp-likeness

Bengt Richter bokr at oz.net
Tue Mar 15 21:00:01 EST 2005


On Wed, 16 Mar 2005 00:36:40 +0100, Marcin 'Qrczak' Kowalczyk <qrczak at knm.org.pl> wrote:

>tar at sevak.isi.edu (Thomas A. Russ) writes:
>
>>> >(defun addn (n)
>>> >	  #'(lambda (x)
>>> >	      (+ x n)))
>>> 
>>> The same as 
>>> def addn(n):
>>> 	def fn(x):
>>> 		return n + x
>>> 	return fn
>>
>> Is this really equivalent?
>>
>> What happens if you call addn more than once with different
>> parameters.  Will you get different functions that you can
>> use simultaneously?
>
>Yes.
>
>It also behaves correctly when a variable it refers to is later
>mutated.
>
>
>BTW, the fact that a closure refers to a variable itself rather to its
>current value can be used to check the true attitude of languages with
>respect to functional programming, by observing how they understand
>their basic loops :-)
>
>Python loses:
>
>>>> closures = []
>>>> for i in range(10):
>...    def add(x):
>...       return x + i
>...    closures.append(add)
>...
>>>> closures[5](1000)
>1009

Fire the coach -- the team can do it ;-)
One way is with the help of a byte-code-hacking decorator:

 >>> from ut.presets import presets
 >>> closures = []
 >>> for i in range(10):
 ...     @presets(i=i)
 ...     def add(x):
 ...         return x + i
 ...     closures.append(add)
 ...
 >>> closures[5](1000)
 1005

 >>> import dis
 >>> dis.dis(closures[5])
   2           0 LOAD_CONST               1 (5)
               3 STORE_FAST               1 (i)

   4           6 LOAD_FAST                0 (x)
               9 LOAD_FAST                1 (i)
              12 BINARY_ADD
              13 RETURN_VALUE
 >>> dis.dis(closures[3])
   2           0 LOAD_CONST               1 (3)
               3 STORE_FAST               1 (i)

   4           6 LOAD_FAST                0 (x)
               9 LOAD_FAST                1 (i)
              12 BINARY_ADD
              13 RETURN_VALUE

Of course, if you want to do it without byte code hacking, you can:

 >>> closures2 = list((lambda i: lambda x: x + i)(i) for i in xrange(10))
 >>> closures2[5](1000)
 1005
 >>> dis.dis(closures2[5])
   1           0 LOAD_FAST                0 (x)
               3 LOAD_DEREF               0 (i)
               6 BINARY_ADD
               7 RETURN_VALUE
 >>> closures2[3](1000)
 1003

Or same thing without lambda:

 >>> def mkadd(i):
 ...     def add(x): return x + i
 ...     return add
 ...
 >>> closures3 = [mkadd(i) for i in xrange(10)]
 >>> closures3[5](1000)
 1005
 >>> closures3[3](1000)
 1003
 >>> dis.dis(closures3[5])
   2           0 LOAD_FAST                0 (x)
               3 LOAD_DEREF               0 (i)
               6 BINARY_ADD
               7 RETURN_VALUE

>
>as does Ruby:
>
>$ ruby -e '
>   closures = []
>   for i in 0..9 do
>      closures.push(proc {|x| x + i})
>   end
>   puts closures[5][1000]'
>1009
>
>but Lisp loses too:
>
>> (let ((closures (make-array 10)))
>    (do ((i 0 (+ i 1)))
>        ((= i 10))
>        (setf (svref closures i) #'(lambda (x) (+ x i))))
>    (funcall (svref closures 5) 1000))
>1010
>
>
>Scheme wins:
>
>> (let ((closures (make-vector 10)))
>    (do ((i 0 (+ i 1)))
>        ((= i 10))
>        (vector-set! closures i (lambda (x) (+ x i))))
>    ((vector-ref closures 5) 1000))
>1005
>
>and what is perhaps surprising, Perl wins:
>
>$ perl -e '
>   foreach my $i (0..9) {
>      push @closures, sub {$_[0] + $i}
>   }
>   print $closures[5](1000), "\n"'
>1005
>
>
>If you think it's unlikely that one would want to keep a closure
>referring to a loop control variable longer than the loop iteration
>which has created it, think about the loop body spawning a thread.
>
Just do what you need to do. Python usually provides a way ;-)
Or do you just want what you want using your idea of the right spelling? ;-)

Regards,
Bengt Richter



More information about the Python-list mailing list