57

我看到Practical Common Lisp用于(defvar *db* nil)设置全局变量。不是可以setq用于相同的目的吗?

使用defvarvs.的优点/缺点是setq什么?

4

4 回答 4

57

有几种方法可以引入变量。

DEFVARDEFPARAMETER引入了全局动态变量。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

局部变量通过DEFUNLAMBDALETMULTIPLE-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。那么它们将是动态变量。

接下来,还有几种形式可以将变量设置为新值。 SETSETQSETF等。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* (带有周围的*字符),以明确它是一个全局特殊变量。其次,它应该在之前DEFVARDEFPARAMETER之前声明过。

一个典型的 Lisp 编译器会抱怨上面的变量是未声明的。由于 Common Lisp 中不存在全局词法变量,编译器必须为动态查找生成代码。一些编译器然后说,好吧,我们假设这是一个动态查找,让我们声明它是特殊的- 因为无论如何这是我们假设的。

于 2010-10-04T16:45:23.587 回答
22

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
于 2010-10-04T14:34:47.103 回答
9

DEFVAR建立一个新变量。 SETQ分配给一个变量。

如果您 SETQ 到一个尚不存在的变量,我使用的大多数 Lisp 实现都会发出警告。

于 2010-10-04T14:03:03.690 回答
8

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 纠正我。

于 2010-10-04T14:03:23.773 回答