我看到Practical Common Lisp使用(defvar *db* nil)
来设置全局变量。使用setq
达到同样的目的不可以吗?
使用defvar
与setq
相比,有什么优缺点呢?
我看到Practical Common Lisp使用(defvar *db* nil)
来设置全局变量。使用setq
达到同样的目的不可以吗?
使用defvar
与setq
相比,有什么优缺点呢?
有几种引入变量的方式。
DEFVAR和DEFPARAMETER引入全局动态变量。 DEFVAR
可选地将其设置为某个值,除非已定义。 DEFPARAMETER
始终将其设置为提供的值。
SETQ不引入变量。
(defparameter *number-of-processes* 10)
(defvar *world* (make-world)) ; the world is made only once.
x
、y
、stream
、limit
等名称来DEFVAR
变量。为什么?因为这些变量将被声明为特殊变量,难以撤消。特殊声明是全局的,变量的所有后续使用都将使用动态绑定。
不好的例子:
(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
BETTER: 在变量名称中使用 *
标记特殊变量!
(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))
SETQ
和 SETF
可以设置词法和特殊(动态)变量。(setq world (make-new-world))
SETQ
将设置变量。但是一些实现可能会在不知道它时声明变量为SPECIAL(CMU Common Lisp默认情况下就是这样)。这几乎总是不是人们想要的。如果你知道自己在做什么,可以在非正式场合使用它,但不要用于代码中。(defun make-shiny-new-world ()
(setq world (make-world 'shiny)))
*world*
(用周围的*
字符),以明确它是全局特殊变量。其次,在声明之前应该使用DEFVAR
或DEFPARAMETER
进行声明。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
词法变量的一个常见用途是定义闭包,以模拟具有状态的对象。在第一个示例中,fy
的绑定环境中的变量y
有效地成为该函数的私有状态。
defvar
仅在变量未被分配时才会将值分配给变量。因此,对*x*
的以下重新定义不会更改原始绑定:
> (defvar *x* 400)
*X*
> *x*
100
setq
为*x*
分配一个新值:> (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来定义变量,这些变量将包含数据,即使您对使用变量的源代码进行更改,您仍希望保留这些数据。”*db*
的全局数据库:
http://www.gigamonkeys.com/book/variables.html
(defvar *db* nil)
如果你在REPL中开始使用它,添加、删除等操作,但是当你更改包含defvar表单的源文件时,重新加载该文件不会清除*db*
和你可能进行的所有更改... 我相信setq
和defparameter
会清除它。如果我错了,希望有更有经验的Lisper来指正。
(defvar x foo)
,而是使用(defvar *x* foo)
,这样你犯错的可能性要小得多)的原因之一。 - Vatine