将“变量名称”传递给defvar

时间:2015-02-19 17:36:39

标签: common-lisp

我现在已经挣扎了两天了,我找不到答案。

我想要的是定义三个变量abc,每个变量的值为0.

朴素: (dolist (lbl '(a b c)) (defvar lbl 0))

没有做我想做的事。 abc仍然未定义,lbl现在的值为0.

我想我可能会理解为什么这不起作用:defvar是一个宏,而不是一个函数,因此我传递了表单 {{1} },而不是标签的当前值(依次为lblab我认为。

但是在最终的宏观扩展中,最终不应该将(?)或(?)链接到我想要的值?显然不是,因为它无法完成或者我做错了。

我想明白:

  1. 如何使这项工作:c
  2. 引擎盖下出了什么问题。我觉得它与符号或引用运算符的机制有关。

4 个答案:

答案 0 :(得分:2)

类似:

(dolist (lbl '(a b c))
  (let ((lbl 0))
    (print lbl)))

为什么lbl 0而不是abc

因为LET绑定了符号lbl而不是其值。

(DEFVAR FOO 3)类似。

想象一下以下代码:

(DEFVAR FOO 3)
(LET ((FOO 3)) ...)

现在,如果我们编译此代码,Lisp编译器会识别DEFVAR声明,现在知道FOO是一个特殊的全局变量。因此,在let表单中FOO将动态绑定。

比较以下代码:

(dolist (v '(FOO)) (eval `(DEFVAR ,v 3)))
(LET ((FOO 3)) ...)

编译器不会看到DEFVAR,也不知道它应该是一个全局特殊变量。在LET表单中,FOO将具有词法绑定。

因此DEFVAR需要是一个在编译时知道符号的宏(!),它扩展为一个表单,通知编译器该符号是一个特殊的全局变量。表单还会在执行时设置值。

因此,从变量列表创建多个DEFVAR声明的最佳方法是编写一个宏,该宏扩展为具有多个PROGN的{​​{1}}形式。在DEFVAR内,编译器仍然会识别它们。

PROGN

实施为:

CL-USER 21 > (pprint (macroexpand '(defvar* (a b c) 0)))

(PROGN (DEFVAR A 0) (DEFVAR B 0) (DEFVAR C 0))

请注意,检查变量是否真的作为符号提供是有意义的。

答案 1 :(得分:1)

defvar是一种特殊形式,可确保其第一个参数的符号是​​绑定变量。如果变量未绑定,则第二个参数的计算表达式将成为绑定变量值。因此:

(defvar *x* 10) ; if *x* was not bound it's now 10
(defvar *x* 20) ; since *x* is defined nothing happens

请注意,*x*未经过评估,但未经评估使用。为了通过使用一个变量来获得相同的功能,该变量的计算结果为您希望作为全局范围内的变量存在的符号,您需要执行以下操作:

(defvar b 10)
(dolist (lbl '(a b c)) 
  (when (not (boundp lbl))
    (setf (symbol-value lbl) 0)))

尽管如此,那些尚未绑定的人与defvar一样变得特别,但至少你会得到同样的行为:

(list a b c) ; => (0 10 0)

也许你应该这样做:

(defvar *a* 0)
(defvar *b* 0)
(defvar *c* 0)

如果你有很多变量,你可以这样做:

(defmacro defvars (lst value)
  (loop :for e :in lst
        :collect `(defvar ,e ,value) :into result
        :finally (return (cons 'progn result))))

(defparameter *w* 10)
(defvars (*q* *w* *e*) 1)
(list *q* *w* *e* ; ==> (1 10 1)

此外,吸收全局变量非常重要。一旦特殊,它将遵循动态绑定。例如。

(defun test ()
  (let ((*b* 15))
    (test2)))

(defun test2 ()
  *b*)

(test) ; ==> 15 

答案 2 :(得分:1)

以下是一些选项:

使用eval,构建defvar表达式:

(dolist (lbl '(a b c))
  (eval `(defvar ,lbl 0))

proclaim setfsymbol-value set(注意:deprecateddefvar,自1994年起为其值得):

(dolist (lbl '(a b c))
  (proclaim `(special ,lbl))
  (setf (symbol-value lbl) 0))

这实际上主要是special的作用(参见链接页面中的注释),但每个Lisp实现通常也会记录源文件的位置,就像它们对其他定义宏一样。


在幕后,defvar是一个宏,它在当前dynamic extent中生成变量dynamic environment(即boundp绑定;注意:此处没有可移植的撤消!),如果还没有绑定,可以选择初始化它。

它是一个宏的事实意味着它不会评估它的参数,所以它可以从字面上取变量名,它就是这样。因此,(defvar lbl 0)将定义变量lbl存储在lbl变量中的符号。

它可选地初始化变量的事实意味着如果变量是evaluation,则甚至不会评估初始化表达式。因此,如果变量已经初始化,则不会发生其次要影响。这可能是也可能不是。

请注意,这个表达式实际上并没有在宏扩展时进行评估,它在评估扩展时留给评估,在REPL中意味着在宏扩展之后(可能在编译之后,取决于Lisp实现;读取关于compilation和{{3}}的更多内容,非常有趣。)

答案 3 :(得分:0)

重新实施DEFVAR

您可以使用以下函数近似 defvar 的行为:

(defun %defvar (symbol value documentation)
  "Define a global special variable.

symbol---a symbol
value---nil or a function of zero arguments
documentation---nil or a documentation string

returns symbol

Proclaim SYMBOL globally as a special variable.  If VALUE is non-nil,
then if SYMBOL is not already bound, SYMBOL is assigned the value
returned by calling VALUE.  DOCUMENATION is assigned as the
documentation of type variable to for SYMBOL."
  (prog1 symbol
    ;; make it globally special
    (proclaim (list 'special symbol))
    ;; if a value is provided, and symbol isn't
    ;; already bound, set its value to the result
    ;; of calling the value-function
    (when (not (null value))
      (unless (boundp symbol)
        (setf (symbol-value symbol)
              (funcall value))))
    ;; set the documentation
    (setf (documentation symbol 'variable) documentation)))

然后你可以做,例如,

CL-USER> (%defvar '*the-answer* (lambda () 42) "the answer")
*THE-ANSWER*
CL-USER> *the-answer*
42
CL-USER> (documentation '*the-answer* 'variable)
"the answer"

使用原始代码,您可以执行以下操作:

(dolist (lbl '(a b c)) (%defvar lbl (lambda () 0)))

现在,这与 defvar 实际上有什么关系?那么,您现在可以通过执行以下操作来实现类似宏的defvar:

(defmacro define-var (symbol &optional (value nil value-p) documentation)
  `(%defvar
    ',symbol
    ,(if value-p `(lambda () ,value) 'nil)
    ,documentation))

这正如我们所期望的那样扩展:

CL-USER> (macroexpand-1 '(define-var *the-answer* 42 "the answer"))
(%DEFVAR '*THE-ANSWER* (LAMBDA () 42) "the answer")

您实际上可以使用 macroexpand 来查看实现的功能。例如,在SBCL中:

CL-USER> (macroexpand-1 '(defvar *the-answer* 42 "the answer"))
(PROGN
 (EVAL-WHEN (:COMPILE-TOPLEVEL) (SB-IMPL::%COMPILER-DEFVAR '*THE-ANSWER*))
 (SB-IMPL::%DEFVAR '*THE-ANSWER* (UNLESS (BOUNDP '*THE-ANSWER*) 42) 'T
                   "the answer" 'T (SB-C:SOURCE-LOCATION)))

这与我们上面所写的内容有太大的不同,尽管当变量已经以稍微不同的方式绑定时,它处理表单的非评估,并且它已经过了还有一些处理来记录源位置。但总体思路是一样的。

为什么事情没有得到关联"

  

但是在最终的宏观扩张中,最终不应该这样做   链接(?)或评估(?)到我想要的值?

原始代码是:

(dolist (lbl '(a b c)) (defvar lbl 0))

我们可以宏观扩展它以查看它变成了什么(在SBCL中):

CL-USER> (macroexpand '(dolist (lbl '(a b c)) (defvar lbl 0)))
(BLOCK NIL
  (LET ((#:N-LIST1022 '(A B C)))
    (TAGBODY
     #:START1023
      (UNLESS (ENDP #:N-LIST1022)
        (LET ((LBL (TRULY-THE (MEMBER C B A) (CAR #:N-LIST1022))))
          (SETQ #:N-LIST1022 (CDR #:N-LIST1022))
          (TAGBODY (DEFVAR LBL 0)))
        (GO #:START1023))))
  NIL)
T

现在,我们仍然可以在两个地方看到 LBL ,包括(defvar LBL 0)。那么为什么事情没有得到满足"?要看到这一点,我们需要记住中的 defvar 也将进行宏扩展。要什么?这样:

CL-USER> (macroexpand '(DEFVAR LBL 0))
(PROGN
 (EVAL-WHEN (:COMPILE-TOPLEVEL) (SB-IMPL::%COMPILER-DEFVAR 'LBL))
 (SB-IMPL::%DEFVAR 'LBL (UNLESS (BOUNDP 'LBL) 0) 'T NIL 'NIL
                   (SB-C:SOURCE-LOCATION)))

但现在我们看到SBCL的内部人员正在获得名为" LBL&#34 ;;调用(sb-impl ::%defvar' lbl ...)使用符号调用函数 sb-impl ::%defvar lbl ,并且该符号与恰好在源中由相同符号表示的词汇变量之间没有任何关联。毕竟,如果你写:

CL-USER> (let ((a 89))
           (list 'a a))
(A 89)

希望能够获得符号 a 和数字 89 ,对吗? defvar 的宏展开包括使用宏的一个参数的引用调用函数。