Common lisp 将“变量名”传递给defvar

Common lisp 将“变量名”传递给defvar,common-lisp,Common Lisp,我已经为此挣扎了两天,但我找不到答案 我想定义三个变量,a、b和c,每个变量的值为0 天真的: dolist lbl'a b c defvar lbl 0 不做我想做的事。a、 b和c仍然未定义,lbl现在的值为0 我想我可以理解为什么这不起作用:defvar是一个宏,而不是一个函数,因此我将以lbl的形式传递给它,而不是label的当前值,label的当前值依次是a、b、c。我想 但在由此产生的宏观扩张中,lbl最终不应该与我所期望的价值相关联或评估吗?显然不是,要么是因为做不到,要么是我做错

我已经为此挣扎了两天,但我找不到答案

我想定义三个变量,a、b和c,每个变量的值为0

天真的: dolist lbl'a b c defvar lbl 0

不做我想做的事。a、 b和c仍然未定义,lbl现在的值为0

我想我可以理解为什么这不起作用:defvar是一个宏,而不是一个函数,因此我将以lbl的形式传递给它,而不是label的当前值,label的当前值依次是a、b、c。我想

但在由此产生的宏观扩张中,lbl最终不应该与我所期望的价值相关联或评估吗?显然不是,要么是因为做不到,要么是我做错了

我想了解:

如何做到这一点:dolist lbl’a b c defvar lbl 0 引擎盖下面出了什么问题。我感觉它与符号或引号操作符的机制有关。 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)))
(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)))
尽管如此,尚未绑定的两个都不像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 
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)))
(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)))
尽管如此,尚未绑定的两个都不像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 
重新实现DEFVAR 您可以使用如下函数近似defvar的行为:

(defvar b 10)
(dolist (lbl '(a b c)) 
  (when (not (boundp lbl))
    (setf (symbol-value lbl) 0)))
(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)))
这与我们上面所写的没有太大的不同,尽管它在以稍微不同的方式绑定变量时处理表单的非求值,并且它也有一些用于记录源位置的处理。不过,总的想法是一样的

为什么事情没有联系起来 但是,在由此产生的宏观扩张中,lbl最终不应该是一个问题吗 与我想要的价值相关联或评估

原代码为:

(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中。那么,为什么事情不匹配呢?要看到这一点,我们需要记住let中的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的符号;调用sb impl::%defvar'lbl…正在使用符号lbl调用函数sb impl::%defvar,并且该符号与源中碰巧由同一符号表示的词法变量之间没有连接。毕竟,如果你写:

CL-USER> (let ((a 89))
           (list 'a a))
(A 89)
你想得到符号a和数字89,对吗?defvar的宏扩展包括对函数的调用,其中引用了宏的一个参数。

重新实现defvar 您可以使用如下函数近似defvar的行为:

(defvar b 10)
(dolist (lbl '(a b c)) 
  (when (not (boundp lbl))
    (setf (symbol-value lbl) 0)))
(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)))
这与我们上面写的没有太大的不同,尽管它在处理表单的非求值时,变量已经以稍微不同的方式绑定,并且它也有一些用于记录源loca的处理 激动。不过,总的想法是一样的

为什么事情没有联系起来 但是,在由此产生的宏观扩张中,lbl最终不应该是一个问题吗 与我想要的价值相关联或评估

原代码为:

(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中。那么,为什么事情不匹配呢?要看到这一点,我们需要记住let中的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的符号;调用sb impl::%defvar'lbl…正在使用符号lbl调用函数sb impl::%defvar,并且该符号与源中碰巧由同一符号表示的词法变量之间没有连接。毕竟,如果你写:

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

你想得到符号a和数字89,对吗?defvar的宏扩展包括对函数的调用,其中引用了宏的一个参数。

以下是一些选项:

使用,通过构建defvar表达式:

(dolist (lbl '(a b c))
  (eval `(defvar ,lbl 0))
值得注意的是:自1994年以来,就其价值而言:

(dolist (lbl '(a b c))
  (proclaim `(special ,lbl))
  (setf (symbol-value lbl) 0))
这实际上是链接页面中notes的主要内容,但每个Lisp实现通常也会记录源文件位置,就像它们对其他定义宏所做的那样

在引擎盖下,defvar是一个宏,用于生成变量,即在当前;注意:此操作没有可移植的撤消功能!,如果它尚未绑定,则可以选择对其进行初始化

事实上,它是一个宏,这意味着它不计算它的参数,所以它可以按字面上的意思取变量名,它就是这样做的。因此,defvar lbl 0将定义变量lbl,而不是存储在lbl变量中的符号

它可以选择性地初始化变量这一事实意味着,如果变量为空,则甚至不会对初始化表达式求值。因此,如果变量已经初始化,它的次要影响就不会发生。这可能是意料之中的,也可能不是


请注意,此表达式在宏展开时并没有实际求值,而是在展开求值时进行求值,这在REPL中表示在宏展开之后,也可能在编译之后,具体取决于Lisp实现;阅读更多关于和的信息,这很有趣。

以下是一些选项:

使用,通过构建defvar表达式:

(dolist (lbl '(a b c))
  (eval `(defvar ,lbl 0))
值得注意的是:自1994年以来,就其价值而言:

(dolist (lbl '(a b c))
  (proclaim `(special ,lbl))
  (setf (symbol-value lbl) 0))
这实际上是链接页面中notes的主要内容,但每个Lisp实现通常也会记录源文件位置,就像它们对其他定义宏所做的那样

在引擎盖下,defvar是一个宏,用于生成变量,即在当前;注意:此操作没有可移植的撤消功能!,如果它尚未绑定,则可以选择对其进行初始化

事实上,它是一个宏,这意味着它不计算它的参数,所以它可以按字面上的意思取变量名,它就是这样做的。因此,defvar lbl 0将定义变量lbl,而不是存储在lbl变量中的符号

它可以选择性地初始化变量这一事实意味着,如果变量为空,则甚至不会对初始化表达式求值。因此,如果变量已经初始化,它的次要影响就不会发生。这可能是意料之中的,也可能不是

请注意,此表达式在宏展开时并没有实际求值,而是在展开求值时进行求值,这在REPL中表示在宏展开之后,也可能在编译之后,具体取决于Lisp实现;阅读更多关于和的信息,这很有趣。

类似:

(dolist (lbl '(a b c))
  (let ((lbl 0))
    (print lbl)))
为什么lbl是0而不是a,b,c中的一些

因为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声明的最佳方法是编写一个宏,该宏将扩展为具有多个DEFVAR的PROGN形式。在PROGN内部,编译器仍然可以识别它们

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

(PROGN (DEFVAR A 0) (DEFVAR B 0) (DEFVAR C 0))
实施方式如下:

(defmacro defvar* (vars initial-value)
  `(progn
     ,@(loop for var in vars
             do (check-type var symbol)
             collect `(defvar ,var ,initial-value))))
请注意,检查变量是否真正作为符号提供是有意义的。

类似:

(dolist (lbl '(a b c))
  (let ((lbl 0))
    (print lbl)))
为什么lbl是0而不是a,b,c中的一些

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

与DEFVAR FOO 3类似

想象一下跟着鳕鱼 e:

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

比较此代码:

(dolist (v '(FOO)) (eval `(DEFVAR ,v 3)))
(LET ((FOO 3)) ...)
编译器不会看到DEFVAR,也不知道它应该是一个全局特殊变量。在LET形式中,FOO将具有词法绑定

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

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

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

(PROGN (DEFVAR A 0) (DEFVAR B 0) (DEFVAR C 0))
实施方式如下:

(defmacro defvar* (vars initial-value)
  `(progn
     ,@(loop for var in vars
             do (check-type var symbol)
             collect `(defvar ,var ,initial-value))))

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

感谢迄今为止的所有答案。解释:宏通过表单传递,可以对表单进行一次、多次或根本不进行计算。我非常专注,没有意识到变量名是故意不计算的,而是按原样使用的。因此,我可能需要某种宏观策略来达到预期效果,正如一些答案中所建议的那样。感谢迄今为止的所有答案。解释:宏通过表单传递,可以对表单进行一次、多次或根本不进行计算。我非常专注,没有意识到变量名是故意不计算的,而是按原样使用的。因此,我可能需要某种宏观策略来达到预期效果,正如一些答案中所建议的那样。我喜欢dolist lbl'a b c eval'defvar,lbl 0的简单性。它依赖于eval,而eval[在某种程度上是不可取的?]。我不知道在这里使用是否安全/有效。当然,我可以使用一种非常类似的方法,但将其作为宏来编写,但我认为最终读写的时间会稍微长一些,在这种情况下会稍微难理解一些。避免评估值得吗?我想你需要告诉我你的具体需求。如果您想定义全局动态变量,可以使用eval of defvar或publicate后跟setf of symbol value。如果您想定义局部词法变量,就不能这样做,在运行的代码中就不行。在列表树中定义值的变量,实际上是解构绑定,例如解构绑定一个b c列表1 2 3 |…|。还有一个progv,它可以创建任意数量的动态绑定,而不是声明动态变量。例如,项目列表“a”b”c列表1 2 3 |…|。请注意,eval并不是邪恶的,尽管在使用eval之前或者由于尝试时间不足,您应该尝试使用其他方法。在REPL中,E代表eval,有时在宏中计算constantp表达式很有用,因此它有它的用途。我喜欢dolist lbl'a b c eval`defvar,lbl 0的简单性。它依赖于eval,而eval[在某种程度上是不可取的?]。我不知道在这里使用是否安全/有效。当然,我可以使用一种非常类似的方法,但将其作为宏来编写,但我认为最终读写的时间会稍微长一些,在这种情况下会稍微难理解一些。避免评估值得吗?我想你需要告诉我你的具体需求。如果您想定义全局动态变量,可以使用eval of defvar或publicate后跟setf of symbol value。如果您想定义局部词法变量,就不能这样做,在运行的代码中就不行。在列表树中定义值的变量,实际上是解构绑定,例如解构绑定一个b c列表1 2 3 |…|。还有一个progv,它可以创建任意数量的动态绑定,而不是声明动态变量。例如,项目列表“a”b”c列表1 2 3 |…|。请注意,eval并不是邪恶的,尽管在使用eval之前或者由于尝试时间不足,您应该尝试使用其他方法。在REPL中,E代表eval,有时在宏中计算constantp表达式很有用,所以它有它的用途。虽然我希望更深入地理解,但我很感激你说的诚实,也许你应该这样做…:-在我考虑更新的生产代码中,真正的列表要长一点。因此,我同样感谢你的后续宏观分析。虽然我希望更深入地理解,但我感谢你说的诚实,也许你应该做…:-在我考虑更新的生产代码中,真正的列表要长一点。所以我同样感谢你的后续宏观。太棒了!谢谢你帮我看评估的影响。呃。。。等待是否确定编译器不会在后引号形式中看到defvar?我还不明白编译过程中所有操作的顺序,但它并不是依赖于运行时信息。。。你能给我指一个你想解释的来源吗
编译时是如何工作的?@brian\u o:它会如何工作?对于编译器来说,它是数据,而不是代码。记住:编译器编译代码,但不会执行它。。。我不知道。。。优化?你当然是对的,谢谢你的提醒。我还在学习,使用emacs slime sblc。它在很多方面都很好,但我认为我不喜欢将编译、解释、宏扩展和运行时活动分开。我会继续努力的,太棒了!谢谢你帮我看评估的影响。呃。。。等待是否确定编译器不会在后引号形式中看到defvar?我还不明白编译过程中所有操作的顺序,但它并不是依赖于运行时信息。。。你能给我指出一个你想解释编译时如何工作的来源吗?@brian_o:它会怎么做?对于编译器来说,它是数据,而不是代码。记住:编译器编译代码,但不会执行它。。。我不知道。。。优化?你当然是对的,谢谢你的提醒。我还在学习,使用emacs slime sblc。它在很多方面都很好,但我认为我不喜欢将编译、解释、宏扩展和运行时活动分开。我会继续努力的。