当前位置:  开发笔记 > 前端 > 正文

Common Lisp中的动态和词法变量

如何解决《CommonLisp中的动态和词法变量》经验,为你挑选了5个好方法。

我正在阅读Peter Seibel撰写的"Practical Common Lisp"一书.

在第6章"变量"部分"词汇变量和闭包"和"动态,又称特殊,变量". http://www.gigamonkeys.com/book/variables.html

我的问题是两个部分中的示例都显示了(让...)如何影响全局变量,并没有真正说明动态和词汇变量之间的区别.

我理解闭包是如何工作的,但我真的不知道在这个例子中让我们如此特别:

(defvar *x* 10)

(defun foo ()
  (format t "Before assignment~18tX: ~d~%" *x*)
  (setf *x* (+ 1 *x*))
  (format t "After assignment~18tX: ~d~%" *x*))


(defun bar ()
  (foo)
  (let ((*x* 20)) (foo))
  (foo))


CL-USER> (foo)
Before assignment X: 10
After assignment  X: 11
NIL


CL-USER> (bar)
Before assignment X: 11
After assignment  X: 12
Before assignment X: 20
After assignment  X: 21
Before assignment X: 12
After assignment  X: 13
NIL

我觉得这里没什么特别的.外FOO酒吧递增全局X,和包围递增阴影X.有什么大不了的?我不认为这应该如何解释词汇和动态变量之间的区别.然而,这本书继续这样:

那么这是如何工作的呢?LET如何知道当它绑定x时它应该创建动态绑定而不是正常的词法绑定?它知道因为名称已被声明为特殊.12使用DEFVAR和DEFPARAMETER定义的每个变量的名称都自动声明为全局特殊.

如果let使用"正常词法绑定"绑定x会发生什么?总而言之,动态绑定和词法绑定有什么区别?这个例子在动态绑定方面有何特殊之处?



1> Rainer Joswi..:

这是怎么回事?

你说:感觉这里没有什么特别的东西.外部foobar全局递增x,并以增量foo包围阴影.有什么大不了的?letbarx

这里的特殊之处在于LET 可以掩盖其价值*x*.用词汇变量是不可能的.

代码通过声明声明*x*特殊DEFVAR.

FOO现在的价值*x*被查找动态.FOO将采用当前动态绑定,*x*或者,如果没有,则采用符号的符号值*x*.例如,可以引入新的动态绑定LET.

另一方面,词汇变量必须存在于某个词汇环境中.LET,LAMBDA,DEFUN和其他人可以推出这样的词法变量.在这里看到x以三种不同方式介绍的词汇变量:

(let ((x 3))
  (* (sin x) (cos x)))

(lambda (x)
  (* (sin x) (cos x)))

(defun baz (x)
  (* (sin x) (cos x)))

如果我们的代码是:

(defvar x 0)

(let ((x 3))
  (* (sin x) (cos x)))

(lambda (x)
  (* (sin x) (cos x)))

(defun baz (x)
  (* (sin x) (cos x)))

然后在上述所有三个案例中X都是特别的,因为DEFVAR声明X特殊的 - 所有级别的全局.因此,存在将特殊变量声明为的约定*X*.因此,按照惯例,只有周围有恒星的变量才是特殊的.这是一个有用的约定.

在您的代码中,您有:

(defun bar ()
  (foo)
  (let ((*x* 20))
    (foo))
  (foo))

由于*x*已被宣布为特别通过DEFVAR上面的代码中,LET构造引入了一个新的动态绑定*x*.FOO然后被叫.因为里面FOO*x*用途动态绑定,它看起来了当前和发现*x*动态绑定到20.

在当前动态绑定中找到特殊变量的值.

本地特别声明

还有当地special声明:

(defun foo-s ()
  (declare (special *x*))
  (+ *x* 1))

如果变量已被a 或声明为特殊,则可省略本地声明.DEFVARDEFPARAMETERspecial

词法变量直接引用变量绑定:

(defun foo-l (x)
  (+ x 1))

让我们在实践中看到它:

(let ((f (let ((x 10))
           (lambda ()
             (setq x (+ x 1))))))
  (print (funcall f))    ; form 1
  (let ((x 20))          ; form 2
    (print (funcall f))))

这里所有变量都是词汇.在表单2中,LET不会影响X我们的函数f.它不能.该函数使用由...引入的词法绑定变量LET ((X 10).使用表单2中的另一个词法绑定X来围绕调用对我们的函数没有影响.

让我们尝试特殊变量:

(let ((f (let ((x 10))
           (declare (special x))
           (lambda ()
             (setq x (+ x 1))))))
  (print (funcall f))    ; form 1
  (let ((x 20))          ; form 2
    (declare (special x))
    (print (funcall f))))

现在怎么办?那样有用吗?

它不是!

一个表单调用该函数,它尝试查找动态值,X但没有.我们在表单1中收到错误:X未绑定,因为没有动态绑定生效.

表2会的工作,因为LETspecial声明引入了一个动态的结合X.


很棒的解释!没有你我们做什么?

2> Kyle Cronin..:

当变量具有词法范围时,系统会查找定义函数的位置以查找自由变量的值.当变量是动态范围的时,系统会查找调用函数的位置以查找自由变量的值.Common Lisp中的变量默认都是词法; 但是,可以使用defvardefparameter在顶层定义动态范围的变量.

一个更简单的例子

词法范围(与setq):

(setq x 3)

(defun foo () x)

(let ((x 4)) (foo)) ; returns 3

动态范围(使用defvar):

(defvar x 3)

(defun foo () x)

(let ((x 4)) (foo)) ; returns 4

如何知道变量是词法还是动态?它没有.另一方面,当foo找到X的值时,它将首先找到在顶层定义的词法值.然后检查该变量是否应该是动态的.如果是,那么foo会查看调用环境,在这种情况下,调用环境使用let来掩盖X的值为4.

(注意:这是过于简单化,但它有助于可视化不同范围规则之间的差异)


奇怪?但就是这样.ANSI Common Lisp在标准中没有全局词法变量.**你的解释大多是错误的**.LET确实知道变量是否是词汇.这是默认值,变量必须在词法环境中定义.它也知道它何时特殊,因为需要有一个"特殊"声明.DEFVAR就是其中之一.如果你的变量不是词法而且没有声明特殊,那么Lisp系统可以自由地做出各种假设.Common Lisp标准没有说明它应该如何工作.实现将以某种方式处理该问题.
Common Lisp中没有词法全局变量.使用SETQ的示例可能有效,也可能不起作用,具体取决于实现.
Rainer是对的,至少是部分的.SETQ示例在SBCL中不起作用(LET表达式被计算为4)

3> 小智..:

也许这个例子会有所帮助.

;; the lexical version

(let ((x 10)) 
  (defun lex-foo ()
    (format t "Before assignment~18tX: ~d~%" x)
    (setf x (+ 1 x))
    (format t "After assignment~18tX: ~d~%" x)))

(defun lex-bar ()
  (lex-foo)
  (let ((x 20)) ;; does not do anything
    (lex-foo))
  (lex-foo))

;; CL-USER> (lex-bar)
;; Before assignment X: 10
;; After assignment  X: 11
;; Before assignment X: 11
;; After assignment  X: 12
;; Before assignment X: 12
;; After assignment  X: 13

;; the dynamic version

(defvar *x* 10)
(defun dyn-foo ()
  (format t "Before assignment~18tX: ~d~%" *x*)
  (setf *x* (+ 1 *x*))
  (format t "After assignment~18tX: ~d~%" *x*))

(defun dyn-bar()
  (dyn-foo)
  (let ((*x* 20))
    (dyn-foo))
  (dyn-foo))

;; CL-USER> (dyn-bar)
;; Before assignment X: 10
;; After assignment  X: 11
;; Before assignment X: 20
;; After assignment  X: 21
;; Before assignment X: 11
;; After assignment  X: 12

;; the special version

(defun special-foo ()
  (declare (special *y*))
  (format t "Before assignment~18tX: ~d~%" *y*)
  (setf *y* (+ 1 *y*))
  (format t "After assignment~18tX: ~d~%" *y*))

(defun special-bar ()
  (let ((*y* 10))
    (declare (special *y*))
    (special-foo)
    (let ((*y* 20))
      (declare (special *y*))
      (special-foo))
    (special-foo)))

;; CL-USER> (special-bar)
;; Before assignment X: 10
;; After assignment  X: 11
;; Before assignment X: 20
;; After assignment  X: 21
;; Before assignment X: 11
;; After assignment  X: 12



4> Leslie P. Po..:

您也可以告诉Lisp动态绑定局部变量:

(let ((dyn 5))
  (declare (special dyn))
  ... ;; DYN has dynamic scope for the duration of the body
  )



5> Juanito Fata..:

从PCL重写示例.

;;; Common Lisp is lexically scoped by default.

? (setq x 10)
=> 10

? (defun foo ()
    (setf x (1+ x)))
=> FOO

? (foo)
=> 11

? (let ((x 20))
    (foo))
=> 12

? (proclaim '(special x))
=> NIL

? (let ((x 20))
    (foo))
=> 21

另一个来自On Lisp的重要解释,第2.5章范围:

Common Lisp是一个词法范围的Lisp.方案是最古老的方言,具有词汇范围; 在Scheme之前,动态范围被认为是Lisp的定义特征之一.

词法和动态范围之间的区别归结为实现如何处理自由变量.如果符号通过作为参数出现,或者通过变量绑定运算符(如let和do)建立为变量,则符号在表达式中绑定.没有绑定的符号被认为是免费的.在此示例中,范围发挥作用:

(let ((y 7)) 
  (defun scope-test (x)
  (list x y)))

在defun表达式中,x被绑定,y是自由的.自由变量很有意思,因为它们的值应该是什么并不明显.关于绑定变量的值没有不确定性 - 当调用scope-test时,x的值应该是作为参数传递的任何值.但是y的价值应该是多少?这是方言范围规则回答的问题.

在动态范围的Lisp中,为了在执行范围测试时找到自由变量的值,我们回顾一下调用它的函数链.当我们找到y绑定的环境时,y的绑定将是范围测试中使用的绑定.如果我们找不到,我们取y的全局值.因此,在动态范围的Lisp中,y将具有它在调用表达式中具有的值:

> (let ((y 5)) (scope-test 3))
    (3 5)

对于动态范围,当定义范围测试时,它并不意味着y被绑定到7.重要的是,当调用范围测试时,y的值为5.

在词法范围内的Lisp中,我们不是回顾调用函数链,而是回顾定义函数时的包含环境.在词法范围的Lisp中,我们的示例将捕获定义范围测试的y的绑定.这就是Common Lisp会发生的事情:

> (let ((y 5)) (scope-test 3))
    (3 7)

这里,在调用时y与5的绑定对返回值没有影响.

虽然通过声明变量是特殊的仍然可以获得动态范围,但是词法范围是Common Lisp中的默认值.总的来说,Lisp社区似乎很少看到动态范围的传递.一方面,它曾经导致可怕的难以捉摸的错误.但词法范围不仅仅是一种避免错误的方法.正如下一节所示,它也使一些新的编程技术成为可能.

推荐阅读
135369一生真爱_890
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有