Why does this defun closure not behave the same as

2019-08-04 07:47发布

问题:

Consider these two:

(defparameter *lfn*
  (let ((count 0))
    #'(lambda ()
    (incf count))))

(defun testclosure ()
    (let ((count 0))
      #'(lambda ()
      (incf count))))

Why do they behave differently:

CL-USER> (funcall (testclosure))
1
CL-USER> (funcall (testclosure))
1
CL-USER> (funcall *lfn*)
1
CL-USER> (funcall *lfn*)
2

count is closed over in the defparameter version but not in the defun version. Why is this?

回答1:

Sylwester's answer explains this very well, but in a case an example with more explicit side effects makes this any clearer, consider:

CL-USER> (defparameter *foo* (progn (print 'hello) 0))
HELLO 
*FOO*
CL-USER> *foo*
0
CL-USER> *foo*
0

In defining *foo*, the (progn (print 'hello) 0) is evaluated once, so hello is printed, and the value is 0, which becomes the value of *foo*. Evaluating *foo* later just means looking up *foo*'s value (0), not reëvaluating the form that produced its original value. In contrast, consider calling a function whose body is(progn (print 'hello) 0)`:

CL-USER> (defun foo () (progn (print 'hello) 0))
FOO
CL-USER> (foo)
HELLO 
0
CL-USER> (foo)
HELLO 
0
CL-USER> (foo)
HELLO 
0

Each time foo is called, (progn (print 'hello) 0) is evaluated, so hello is printed and 0 is returned. After seeing this example, your code should be a bit clearer.

(defparameter *lfn*
  (let ((count 0))
    #'(lambda ()
    (incf count))))

(let ...) is evaluated once, and the closure that that evaluation produces its the value of *lfn*. On the other hand, in

(defun testclosure ()
    (let ((count 0))
      #'(lambda ()
      (incf count))))

(let ...) is evaluated every time that testclosure is called, a new closure is returned each time.



回答2:

When you are making *lfn* you are creating a function within one closure.. Calling it will increase the closed over count and evaluate to it.

testclosure does the same as what you did with *lfm* for every time it gets called. Thus:

(defparameter *lfn2* (testclosure))
(funcall *lfn2*) ; ==> 1
(funcall *lfn2*) ; ==> 2
(funcall *lfn2*) ; ==> 3

Will make exactly the same as *lfn* such that consecutive calls to it will increase the value returned. However

(funcall (testclosure)) ; ==> 1 (and the closure can be recycled)
(funcall (testclosure)) ; ==> 1 (and the closure can be recycled)

Here you are doing funcall on a newly created closure, that you don't store for consecutive calls, so it will return 1. Then you are doing funcall again on a completely new closure that you also don't store and it's first call also evaluates to 1.

So the answer is, count is closed over in both, but in you example you were creating a new closure and using it only once, several times.



回答3:

The value of *lfn* is a closure.

The function testclosure returns a new closure each time you call it.