【发布时间】:2011-04-20 19:55:04
【问题描述】:
我看到Practical Common Lisp 使用(defvar *db* nil) 来设置一个全局变量。用setq做同样的目的不好吗?
使用defvar 与setq 相比有哪些优点/缺点?
【问题讨论】:
标签: variables lisp common-lisp assign variable-declaration
我看到Practical Common Lisp 使用(defvar *db* nil) 来设置一个全局变量。用setq做同样的目的不好吗?
使用defvar 与setq 相比有哪些优点/缺点?
【问题讨论】:
标签: variables lisp common-lisp assign variable-declaration
答案就在这里。很清楚。
https://www.tutorialspoint.com/lisp/lisp_variables.htm
全局变量通常使用defvar 构造声明。
由于 LISP 中没有变量的类型声明,因此您可以直接使用 setq 构造为符号指定值。
与全局变量一样,局部变量也可以使用setq 构造来创建。
【讨论】:
setq 构造来创建。" 你不能可靠地做到这一点; setq、setf 和朋友仅适用于 现有 绑定的突变。 HyperSpec 中没有指定通过(setq foo 42) 引入变量的行为(SBCL 甚至发出警告),并且已知尝试这样做的程序会表现出令人惊讶的行为。另请注意,TutorialsPoint 不是 Common Lisp 的良好或可靠资源或参考。
有几种方法可以引入变量。
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 中不存在全局词法变量,编译器必须为动态查找生成代码。一些编译器然后说,好吧,我们假设这是一个动态查找,让我们将它声明为 special - 因为无论如何这都是我们假设的。
【讨论】:
(defvar x foo),他们使用(defvar *x* foo),这样你犯错误的可能性就会大大降低.
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-ed 的内容。 Seibel 在本书后面(第 6 章)中说:“实际上,您应该使用 DEFVAR 定义变量,这些变量将包含您想要保留的数据,即使您对使用该变量的源代码进行了更改。”
http://www.gigamonkeys.com/book/variables.html
例如,如果您在“简单数据库”一章中有一个用于数据库的全局 *db*:
(defvar *db* nil)
...然后您开始在 REPL 中使用它 - 添加、删除内容等 - 但随后您对包含该 defvar 表单的源文件进行了更改,重新加载该文件不会清除 *db* 和您可能所做的所有更改...我相信setq 会,defparameter 也会。如果我错了,请更有经验的 Lisper 纠正我。
【讨论】: