【问题标题】:setq and defvar in LispLisp 中的 setq 和 defvar
【发布时间】:2011-04-20 19:55:04
【问题描述】:

我看到Practical Common Lisp 使用(defvar *db* nil) 来设置一个全局变量。用setq做同样的目的不好吗?

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

【问题讨论】:

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


    【解决方案1】:

    答案就在这里。很清楚。

    https://www.tutorialspoint.com/lisp/lisp_variables.htm

    全局变量通常使用defvar 构造声明。

    由于 LISP 中没有变量的类型声明,因此您可以直接使用 setq 构造为符号指定值。

    与全局变量一样,局部变量也可以使用setq 构造来创建。

    【讨论】:

    • "像全局变量一样,局部变量也可以使用setq 构造来创建。" 你不能可靠地做到这一点; setqsetf 和朋友仅适用于 现有 绑定的突变。 HyperSpec 中没有指定通过(setq foo 42) 引入变量的行为(SBCL 甚至发出警告),并且已知尝试这样做的程序会表现出令人惊讶的行为。另请注意,TutorialsPoint 不是 Common Lisp 的良好或可靠资源或参考。
    • 正如目前所写,您的答案尚不清楚。请edit 添加其他详细信息,以帮助其他人了解这如何解决所提出的问题。你可以找到更多关于如何写好答案的信息in the help center
    【解决方案2】:

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

    DEFVARDEFPARAMETER 引入了全局 动态变量。 DEFVAR 可以选择将其设置为某个值,除非它已被定义。 DEFPARAMETER 始终将其设置为提供的值。 SETQ 没有引入变量。

    (defparameter *number-of-processes* 10)
    
    (defvar *world* (make-world))     ; the world is made only once.
    

    请注意,您可能永远不想 DEFVAR 变量名称为 xystreamlimit,... 为什么?因为这些变量会被声明为特殊的并且很难撤销。特殊声明是全局的,变量的所有进一步使用都将使用动态绑定。

    不好:

    (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 中不存在全局词法变量,编译器必须为动态查找生成代码。一些编译器然后说,好吧,我们假设这是一个动态查找,让我们将它声明为 special - 因为无论如何这都是我们假设的。

    【讨论】:

    • 令我惊讶的是,一旦您使用 defvar 将变量声明为全局变量,就无法创建具有相同名称的局部变量,因此 (let ((x 1)) x ) 如果 x 由 defvar 声明,可能会产生意想不到的结果。
    • @ian 这就是很多人使用“耳罩”的原因之一(也就是说,他们从不使用(defvar x foo),他们使用(defvar *x* foo),这样你犯错误的可能性就会大大降低.
    【解决方案3】:

    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
    

    【讨论】:

    • 很遗憾这是错误的。 a (setq y 200) 对未声明/定义的变量的确切影响是未定义的。 Common Lisp 也没有全局词法变量。 SETQ 设置一个变量。而已。动态变量或词法变量,取决于提供的变量。 LET 绑定。 SETQ 集。
    • 也不能定义函数 CL:PRINT,因为该名称已被标准函数采用。 FORMAT 打印到流,而不是文件。
    • @Rainer 感谢您指出不准确之处。我已经更新了答案。
    【解决方案4】:

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

    【讨论】:

      【解决方案5】:

      DEFVAR 建立一个新变量。 SETQ 赋值给一个变量。

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

      【讨论】:

        猜你喜欢
        • 2013-08-12
        • 2012-02-14
        • 1970-01-01
        • 2010-11-01
        • 2010-10-26
        • 1970-01-01
        • 2016-08-01
        • 1970-01-01
        相关资源
        最近更新 更多