Chinaunix

标题: 能不能讲一下closure,正在看practical common lisp,就是理解不了这个概念 [打印本页]

作者: xdshting    时间: 2009-03-08 23:39
标题: 能不能讲一下closure,正在看practical common lisp,就是理解不了这个概念
书上的例子是
(let ((count 0)) #'(lambda () (setf count (1+ count))))

the reference to count inside the LAMBDA form should be legal according to the rules of lexical scoping. Yet the anonymous function containing the reference will be returned as the value of the LET form and can be invoked, via FUNCALL, by code that’s not in the scope of the LET. So what happens? As it turns out, when count is a lexical variable, it just works. The binding of count created when the flow of control entered the LET form will stick around for as long as needed, in this case for as long as someone holds onto a reference to the function object returned by the LET form. The anonymous function is called a closure because it “closes over” the binding created by the LET.
The key thing to understand about closures is that it’s the binding, not the value of the variable, that’s captured. Thus, a closure can not only access the value of the variables it closes over but can also assign new values that will persist between calls to the closure. For instance, you can capture the closure created by the previous expression in a global variable like this:

(defparameter *fn* (let ((count 0)) #'(lambda () (setf count (1+ count)))))

Then each time you invoke it, the value of count will increase by one.

CL-USER> (funcall *fn*)
1
CL-USER> (funcall *fn*)
2
CL-USER> (funcall *fn*)
3
基本上没看明白,而且红色的地方也有点奇怪

[ 本帖最后由 xdshting 于 2009-3-8 23:44 编辑 ]
作者: win_hate    时间: 2009-03-09 00:04
1. let 为 count 绑定了一个值 0,返回的匿名函数可以引用,修改它。这称为 static scope (lexical),类似于 c 语言中的静态变量。粗略地讲,闭包包住了一个私有环境。

2. 返回的匿名函数若与变量 *fn* 绑定,则可以通过 *fn* 来调用,访问并修改匿名函数的“私有环境”。

3. lisp 支持自动垃圾回收,当匿名函数不再被使用时,资源会被回收。(... will stick around for as long as needed ...)

4. 1+ 是个函数,对参数执行加 1 操作.
作者: win_hate    时间: 2009-03-09 00:18
scheme 是第一个引入静态作用域的 lisp,后来 Guy Steel 跑去开发 common lisp,把这个也加进去了。

有一些 lisp 是动态作用域的,比如 elisp. 如果在 emacs 中执行


  1. (setq *fn* (let ((count 0)) #'(lambda () (setq count (1+ count)))))


  2. (funcall *fn*)
复制代码


就会出错,因为: Debugger entered--Lisp error: (void-variable count)

显然 *fn* 找不到 count 了。如果在函数外面定义一个 count,值为 100


  1. (setq *fn* (let ((count 0)) #'(lambda () (setq count (1+ count)))))

  2. (setq count 100)

  3. (funcall *fn*)
复制代码


则 funcall 的结果为 101,显然跟 let 里头的 count 是无关的。

[ 本帖最后由 win_hate 于 2009-3-9 00:19 编辑 ]
作者: xdshting    时间: 2009-03-09 00:22
每次运行(funcall (let ((count 0)) #'(lambda () (setf count (1+ count))))) 的值都是1

(defparameter *fn* (let ((count 0)) #'(lambda () (setf count (1+ count)))))

Then each time you invoke it, the value of count will increase by one.

CL-USER> (funcall *fn*)
1
CL-USER> (funcall *fn*)
2
CL-USER> (funcall *fn*)
3
这怎么解释,红色部分不就是说用defparameter代替后面的(let...)吗?
另外
(1+ count)与(+ 1 count)有区别吗?

谢谢

[ 本帖最后由 xdshting 于 2009-3-9 00:23 编辑 ]
作者: win_hate    时间: 2009-03-09 00:34
1、

  1. 每次运行(funcall (let ((count 0)) #'(lambda () (setf count (1+ count))))) 的值都是1
复制代码

因为每次运行都得到一个新的匿名函数,它也包有一个新的环境,在该环境中,count 值为 0。

2、

  1. (defparameter *fn* (let ((count 0)) #'(lambda () (setf count (1+ count)))))
复制代码

以后通过 *fn* 去调用,let 只执行了一次,只有一个与 *fn* 关联的匿名函数(相应的私有空间)。

看看下面的演示


  1. [1]> (defparameter f (let ((count 0)) #'(lambda () (setf count (1+ count)))))F
  2. [2]> (defparameter g (let ((count 0)) #'(lambda () (setf count (1+ count)))))
  3. G
  4. [3]> (funcall f)
  5. 1
  6. [4]> (funcall f)
  7. 2
  8. [5]> (funcall f)
  9. 3
  10. [6]> (funcall g)
  11. 1
  12. [7]>
复制代码


f, g 的私有空间是不同的。直接 funcall let 的结果相当于每次都生成一个新函数,所以值也都是 1 了。

3、1+ 是个函数名字,这个函数接受一个参数;+ 是另外一个函数,接受 0 个或多个参数.


  1. > (funcall '1+ 2)
  2. 3
  3. > (funcall '+ 1 2)
  4. 3
  5. > (funcall '+ 2 1)
  6. 3
复制代码


(1+ count)与(+ 1 count)的结果是一样的,但调用的是不同的函数。
作者: xdshting    时间: 2009-03-09 00:50
谢谢,现在明白一些了

看到您用
(funcall '+ 3 4)
我用
(funcall #'+ 3 4)

可以得到相同的结果,我觉得你用的形式不对阿,为什么能执行?
作者: win_hate    时间: 2009-03-09 01:05
原帖由 xdshting 于 2009-3-9 00:50 发表
谢谢,现在明白一些了

看到您用
(funcall '+ 3 4)
我用
(funcall #'+ 3 4)

可以得到相同的结果,我觉得你用的形式不对阿,为什么能执行?


参考这个连接,http://www.lispworks.com/documen ... c/Body/f_funcal.htm

Syntax:

funcall function &rest args => result*

......

funcall applies function to args. If function is a symbol, it is coerced to a function as if by finding its functional value in the global environment.


所以两种形式都是可以的。
作者: flw    时间: 2009-03-09 09:06
1+ 是个函数名称呢?
还是仅仅只是 + 这个函数 curry 化的结果?
作者: xdshting    时间: 2009-03-09 09:22
谢谢了
再看ansi common lisp关于closure的介绍,又有一点不明白的地方
书上的例子
( let ((counter 0))
    (defun reset ()
       (setf counter 0))
    (defun stamp ()
       (setf counter (+ counter 1))))

CL-USER> ( l i s t (stamp) (stamp) ( reset ) (stamp))
          ( 1 2 0 1)

list是怎么看到stamp和reset函数的?这两个函数在let内部定义的阿

[ 本帖最后由 xdshting 于 2009-3-9 09:38 编辑 ]
作者: win_hate    时间: 2009-03-09 10:46
原帖由 flw 于 2009-3-9 09:06 发表
1+ 是个函数名称呢?
还是仅仅只是 + 这个函数 curry 化的结果?


lisp 不支持自动 curry 化,1+ 是专门另做的一个函数。当然本质上可以看成 1+x 的 curry

在 emacs 中,对 1+ 的描述为:

1+ is a built-in function in `C source code'.

(1+ NUMBER)

Return NUMBER plus one.  NUMBER may be a number or a marker.
Markers are converted to integers.

作者: win_hate    时间: 2009-03-09 10:51
原帖由 xdshting 于 2009-3-9 09:22 发表
谢谢了
再看ansi common lisp关于closure的介绍,又有一点不明白的地方
书上的例子
( let ((counter 0))
    (defun reset ()
       (setf counter 0))
    (defun stamp ()
       (setf counter (+ c ...


问得好。在 scheme 中,这样是不行的。如果你不说,我还真不知道 common lisp 有这一手。

我对 common lisp 不了解,还是等对 common lisp 比较熟悉的朋友来回答吧。
作者: xdshting    时间: 2009-03-09 11:05
1,看了一些对closure的介绍

有一个感觉,有点像java里的封装吗,把free variable看作私有变量,只有通过对外接口去访问他

这样理解对不对?

2,另外,还是有点不明白,什么时候变量可以递增,什么时候他被重新初始化

(defparameter *fn* (let ((count 0)) #'(lambda () (setf count (1+ count)))))

以后通过 *fn* 去调用,let 只执行了一次,只有一个与 *fn* 关联的匿名函数(相应的私有空间)。

为什么只调用一次,是defparameter捣鬼吗?
作者: win_hate    时间: 2009-03-09 11:12
我查了一个:

http://community.schemewiki.org/?scheme-faq-language

scheme 的 faq,在里面的 Is there a way to define top-level closures?  一节说到:

In Common Lisp, the defun construct always creates a top-level definition, e.g.


所以你能在 let 外访问 let 中定义的函数。

而在 scheme 中, let 通常是个 sugar,被转换成 curry 函数。所以在 let 的外部就不能直接访问 let  内部定义的函数了(除非把它作为 let 的最后一项返回)。

[ 本帖最后由 win_hate 于 2009-3-9 11:23 编辑 ]
作者: win_hate    时间: 2009-03-09 11:22
原帖由 xdshting 于 2009-3-9 11:05 发表
>>1,看了一些对closure的介绍

有一个感觉,有点像java里的封装吗,把free variable看作私有变量,只有通过对外接口去访问他

这样理解对不对?



2,另外,还是有点不明白,什么时候变量可以递增,什么时候 ...


1、不懂 java,不过 “把free variable看作私有变量” 是对的。

2、就好比,如果你多次执行 (+ 1 1),则每次都会计算一个加法。
但 (setq a (+ 1 1)) 后,每次执行 a,都直接得到 2,不会再执行一次加法。

(defparameter *fn* (let ((count 0)) #'(lambda () (setf count (1+ count)))))


求值方式为:

a. 求出 (let ((count 0)) #'(lambda () (setf count (1+ count))) 的值。此时创建了匿名函数;
b. 把匿名函数入口绑定到 *fn* 上。

以后再执行 *fn* 时,就直接访问匿名函数了。

[ 本帖最后由 win_hate 于 2009-3-9 11:25 编辑 ]
作者: xdshting    时间: 2009-03-09 11:27
原帖由 win_hate 于 2009-3-9 11:12 发表
我查了一个:

http://community.schemewiki.org/?scheme-faq-language

scheme 的 faq,在里面的 Is there a way to define top-level closures?  一节说到:



所以你能在 let 外访问 let 中定义的函 ...


在很多地方都看到top-level这个词,您能不能详细解释以下

给我的感觉有一点像是全局的意思
作者: win_hate    时间: 2009-03-09 11:36
就是全局吧。
作者: lixuzhang    时间: 2009-05-21 20:45
原帖由 xdshting 于 2009-3-8 23:39 发表
书上的例子是
(let ((count 0)) #'(lambda () (setf count (1+ count))))

the reference to count inside the LAMBDA form should be legal according to the rules of lexical scoping. Yet the anonymou ...



C语言只能返回函数指针,所以函数指针只是函数指针。

但Closure就是将函数指针与上下文的变量绑定在一起了;所以返回的函数指针就不再一样。


根据count的值的不同,返回的函数指针(lambda函数)与count形成的闭包,就形成了各种不同的函数(部分变量已经绑定不同值)。

(...
    (lambda (x)
        (+ n x))

根据n的不同值,返回的lambda函数就是 x+1, 或x+2,或x+3函数(变量n已经绑定)。




欢迎光临 Chinaunix (http://bbs.chinaunix.net/) Powered by Discuz! X3.2