我看到Practical Common Lisp用于(defvar *db* nil)
设置全局变量。不是可以setq
用于相同的目的吗?
使用defvar
vs.的优点/缺点是setq
什么?
我看到Practical Common Lisp用于(defvar *db* nil)
设置全局变量。不是可以setq
用于相同的目的吗?
使用defvar
vs.的优点/缺点是setq
什么?
有几种方法可以引入变量。
DEFVAR和DEFPARAMETER引入了全局动态变量。DEFVAR
可选择将其设置为某个值,除非它已被定义。DEFPARAMETER
始终将其设置为提供的值。
SETQ不引入变量。
(defparameter *number-of-processes* 10)
(defvar *world* (make-world)) ; the world is made only once.
请注意,您可能永远不想DEFVAR
使用名称如x
, y
, stream
, limit
, ... 为什么?因为这些变量会被声明为特殊的并且很难撤销。特殊声明是全局的,变量的所有进一步使用都将使用动态绑定。
坏的:
(defvar x 10) ; global special variable X, naming convention violated
(defvar y 20) ; global special variable Y, naming convention violated
(defun foo ()
(+ x y)) ; refers to special variables X and y
(defun bar (x y) ; OOPS!! X and Y are special variables
; even though they are parameters of a function!
(+ (foo) x y))
(bar 5 7) ; -> 24
更好:始终*
在名称中标记特殊变量!
(defvar *x* 10) ; global special variable *X*
(defvar *y* 20) ; global special variable *Y*
(defun foo ()
(+ *x* *y*)) ; refers to special variables X and y
(defun bar (x y) ; Yep! X and Y are lexical variables
(+ (foo) x y))
(bar 5 7) ; -> 42
局部变量通过DEFUN、LAMBDA、LET、MULTIPLE-VALUE-BIND和许多其他方法引入。
(defun foo (i-am-a-local-variable)
(print i-am-a-local-variable))
(let ((i-am-also-a-local-variable 'hehe))
(print i-am-also-a-local-variable))
现在,默认情况下,上述两种形式的局部变量是词法的,除非它们被声明为SPECIAL。那么它们将是动态变量。
接下来,还有几种形式可以将变量设置为新值。 SET、SETQ、SETF等。SETQ
并且SETF
可以设置词法和特殊(动态)变量。
可移植代码需要设置已声明的变量。标准未定义设置未声明变量的确切效果。
所以,如果你知道你的 Common Lisp 实现是做什么的,你可以使用
(setq world (make-new-world))
在顶层的Read-Eval-Print-Loop中。但不要在代码中使用它,因为效果不可移植。通常SETQ
会设置变量。但是某些实现也可能在它不知道变量SPECIAL时声明它(CMU Common Lisp 默认情况下会这样做)。这几乎总是不是人们想要的。如果您知道自己在做什么,请将其用于临时用途,但不适用于代码。
同样在这里:
(defun make-shiny-new-world ()
(setq world (make-world 'shiny)))
首先,这些变量应该写成*world*
(带有周围的*
字符),以明确它是一个全局特殊变量。其次,它应该在之前DEFVAR
或DEFPARAMETER
之前声明过。
一个典型的 Lisp 编译器会抱怨上面的变量是未声明的。由于 Common Lisp 中不存在全局词法变量,编译器必须为动态查找生成代码。一些编译器然后说,好吧,我们假设这是一个动态查找,让我们声明它是特殊的- 因为无论如何这是我们假设的。
defvar
引入动态变量,同时setq
用于为动态或词法变量赋值。在调用函数的环境中查找动态变量的值,而在定义函数的环境中查找词法变量的值。以下示例将使区别变得清晰:
;; dynamic variable sample
> (defvar *x* 100)
*X*
> (defun fx () *x*)
FX
> (fx)
100
> (let ((*x* 500)) (fx)) ;; gets the value of *x* from the dynamic scope.
500
> (fx) ;; *x* now refers to the global binding.
100
;; example of using a lexical variable
> (let ((y 200))
(let ((fy (lambda () (format t "~a~%" y))))
(funcall fy) ;; => 200
(let ((y 500))
(funcall fy) ;; => 200, the value of lexically bound y
(setq y 500) ;; => y in the current environment is modified
(funcall fy)) ;; => 200, the value of lexically bound y, which was
;; unaffected by setq
(setq y 500) => ;; value of the original y is modified.
(funcall fy))) ;; => 500, the new value of y in fy's defining environment.
动态变量对于传递默认值很有用。例如,我们可以将动态变量绑定*out*
到标准输出,使其成为所有 io 函数的默认输出。要覆盖此行为,我们只需引入本地绑定:
> (defun my-print (s)
(format *out* "~a~%" s))
MY-PRINT
> (my-print "hello")
hello
> (let ((*out* some-stream))
(my-print " cruel ")) ;; goes to some-stream
> (my-print " world.")
world
词法变量的一个常见用途是定义闭包,以模拟具有状态的对象。在第一个示例中,y
绑定环境中的变量fy
有效地成为该函数的私有状态。
defvar
仅当变量尚未赋值时才会为其赋值。所以下面的重新定义*x*
不会改变原来的绑定:
> (defvar *x* 400)
*X*
> *x*
100
我们可以*x*
使用以下方法分配一个新值setq
:
> (setq *x* 400)
400
> *x*
400
> (fx)
400
> (let ((*x* 500)) (fx)) ;; setq changed the binding of *x*, but
;; its dynamic property still remains.
500
> (fx)
400
defvar
并且defparameter
都引入了全局变量。正如 Ken 所说,setq
赋值给一个变量。
此外,defvar
不会破坏以前defvar
编辑的内容。Seibel 在本书后面(第 6 章)中说:“实际上,您应该使用 DEFVAR 定义变量,这些变量将包含您想要保留的数据,即使您对使用该变量的源代码进行了更改。”
http://www.gigamonkeys.com/book/variables.html
例如,如果您*db*
在“简单数据库”一章中有一个全局数据库:
(defvar *db* nil)
...然后您开始在 REPL 上使用它 - 添加、删除内容等 - 但随后您对包含该 defvar 表单的源文件进行了更改,重新加载该文件并不会消除*db*
您可能拥有的所有更改制造...我相信setq
会,也会defparameter
。如果我错了,请更有经验的 Lisper 纠正我。