Lisp中的setq和defvar

时间:2010-10-04 13:52:06

标签: variables lisp common-lisp assign variable-declaration

我看到Practical Common Lisp使用(defvar *db* nil)来设置全局变量。是否可以将setq用于同一目的?

使用defvarsetq相比有哪些优点/缺点?

4 个答案:

答案 0 :(得分:44)

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

DEFVARDEFPARAMETER介绍全局动态变量。 DEFVAR可选地将其设置为某个值,除非已经定义了它。 DEFPARAMETER始终将其设置为提供的值。 SETQ不会引入变量。

(defparameter *number-of-processes* 10)

(defvar *world* (make-world))     ; the world is made only once.

请注意,您可能永远不会想要DEFVARxystream等名称的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

本地变量与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等。 SETQSETF可以设置词法和特殊(动态)变量。

可移植代码需要设置已声明的变量。标准未定义设置未声明变量的确切效果。

因此,如果您知道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中不存在全局词法变量,因此编译器必须为动态查找生成代码。然后有些编译器说,好吧,我们假设这是一个动态查找,让我们声明它是特殊的 - 因为这是我们所想的。

答案 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)

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

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

答案 3 :(得分:8)

defvardefparameter都引入了全局变量。正如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请纠正我。