我看到Practical Common Lisp使用(defvar *db* nil)
来设置全局变量。是否可以将setq
用于同一目的?
使用defvar
与setq
相比有哪些优点/缺点?
答案 0 :(得分:44)
有几种方法可以引入变量。
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
变量,...为什么?因为这些变量会被声明为特殊的,并且难以撤消。特殊声明是全局的,变量的所有进一步使用都将使用动态绑定。
BAD:
(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中不存在全局词法变量,因此编译器必须为动态查找生成代码。然后有些编译器说,好吧,我们假设这是一个动态查找,让我们声明它是特殊的 - 因为这是我们所想的。
答案 1 :(得分:18)
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
答案 2 :(得分:8)
答案 3 :(得分:8)
defvar
和defparameter
都引入了全局变量。正如Ken所说,setq
分配给变量。
此外,defvar
不会破坏先前的defvar
- ed。 Seibel在本书后面(第6章)中说:“实际上,你应该使用DEFVAR来定义包含你想要保留的数据的变量,即使你对使用变量的源代码进行了更改。”
http://www.gigamonkeys.com/book/variables.html
例如,如果您在简单数据库章节中有数据库的全局*db*
:
(defvar *db* nil)
...然后你开始在REPL上玩它 - 添加,删除东西等等 - 然后你对包含该defvar表单的源文件进行了更改,重新加载该文件不会消除{{1}以及你可能做出的所有改变......我相信*db*
将会setq
。如果我错了,请更有经验的Lisper请纠正我。