在 Common Lisp 中,如何测试变量是否特殊?
In Common Lisp, how to test if variable is special?
我以为我可以通过 Google、SO 或我正在阅读的书籍找到它,但事实证明它难以捉摸。
在我正在学习的实现中,我可以在顶层执行以下操作:
(defvar *foo* 4)
(set 'bar 3)
如果我随后调用 (describe '*foo*)
和 (describe 'bar)
,我得到的描述是 *foo*
是特殊的,bar
是非特殊的(以及其他细节)。
是否有一个函数以符号变量为参数,returns true 或 false 如果它是特殊的?如果是这样,describe
可能是通过调用它部分实现的吗?
上下文:我正在学习 Common Lisp,但在工作中我有一个带有类似于 Common Lisp 的 Lisp 方言的系统,但 describe
功能未实现。这里发生了一些 XY 事情,但我也在尝试理解 Lisp 和 CL。
不支持使用 set
或 setq
定义全局变量。定义全局变量有两种常用方法:
(defparameter *par* 20) ; notice the earmuffs in the name!
(defvar *var* 30) ; notice the earmuffs in the name!
所有的全局变量都是特殊的。无法描述词法范围的变量(非特殊变量)。例如
(let ((x 10))
(describe 'x)) ; ==> X is the symbol X
它描述的不是词法变量而是符号表示。这真的无关紧要,因为您可能永远不需要在 运行 时间内知道,因为您在编写时知道它是绑定词法变量还是全局特殊变量,符合全局变量的耳罩命名约定。
许多 Common Lisp 实现在某些系统依赖包中提供函数 variable-information
。
在 SBCL 中:
* (require :sb-cltl2)
NIL
* (sb-cltl2:variable-information '*standard-output*)
:SPECIAL
NIL
((TYPE . STREAM))
这个函数是作为一些其他功能的一部分被提议包含在 ANSI CL 中的,但没有进入标准。仍然有很多实现。有关文档,请参阅:https://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node102.html
当您在其上创建闭包时,将捕获非特殊变量的环境:
(let ((x 1))
(let ((f (lambda () x)))
(let ((x 2))
(eql 2 (funcall f)))))
;;=> NIL
特殊变量的词法环境不会:
(defvar *x*) ; *x* is special
(let ((*x* 1))
(let ((f (lambda () *x*)))
(let ((*x* 2))
(eql 2 (funcall f)))))
;;=> T
使用这种方法,您可以轻松定义一个 宏,它将扩展为类似于之前的代码,让您确定一个符号是否在全球范围内被宣布为特殊符号:
(defmacro specialp (symbol)
(let ((f (gensym "FUNC-")))
`(let ((,symbol 1))
(let ((,f (lambda () ,symbol)))
(let ((,symbol 2))
(eql 2 (funcall ,f)))))))
(specialp x) ;=> NIL
(specialp *x*) ;=> T
请注意,这不是函数,而是宏。这意味着使用 符号 X 和 *X* 调用 specialp 的宏函数。这很重要,因为我们必须构建使用这些符号的代码。你不能用一个函数来做到这一点,因为没有(可移植的)方法来获取一个符号并创建一个词法环境,该词法环境具有一个具有该名称的词法变量和一个引用它的 lambda 函数。
如果您尝试将它与某些符号一起使用,这也会带来一些风险。例如,在 SBCL 中,如果您尝试将 *standard-output* 绑定到不是流或流指示符的内容,您将收到错误消息:
CL-USER> (specialp *standard-output*)
; in: SPECIALP *STANDARD-OUTPUT*
; (LET ((*STANDARD-OUTPUT* 1))
; (LET ((#:FUNC-1038 (LAMBDA # *STANDARD-OUTPUT*)))
; (LET ((*STANDARD-OUTPUT* 2))
; (EQL 2 (FUNCALL #:FUNC-1038)))))
;
; caught WARNING:
; Constant 1 conflicts with its asserted type STREAM.
; See also:
; The SBCL Manual, Node "Handling of Types"
;
; compilation unit finished
; caught 1 WARNING condition
我认为在 运行 时间*获取此信息的唯一方法是使用 CL 的扩展,如 Rainer 指出的那样,或者使用 eval
。
(defun specialp (x)
(or (boundp x)
(eval `(let (,x)
(declare (ignorable ,x))
(boundp ',x)))))
(缺陷警告:如果变量未绑定但声明为与 nil 不兼容的类型,这可能会引发错误。感谢 Joshua 在他的回答中指出。)
* 宏方法确定它在宏扩展时检查哪个符号,以及该符号在编译时是词法的还是特殊的。这对于检查 repl 中变量的状态很好。如果你想,例如打印包导出的所有特殊变量,但是,您会发现要使用宏版本,您最终必须在调用站点使用 eval
:
(loop for s being the external-symbols of :cl-ppcre
when (eval `(specialp-macro ,s)) do (print s))
我以为我可以通过 Google、SO 或我正在阅读的书籍找到它,但事实证明它难以捉摸。
在我正在学习的实现中,我可以在顶层执行以下操作:
(defvar *foo* 4)
(set 'bar 3)
如果我随后调用 (describe '*foo*)
和 (describe 'bar)
,我得到的描述是 *foo*
是特殊的,bar
是非特殊的(以及其他细节)。
是否有一个函数以符号变量为参数,returns true 或 false 如果它是特殊的?如果是这样,describe
可能是通过调用它部分实现的吗?
上下文:我正在学习 Common Lisp,但在工作中我有一个带有类似于 Common Lisp 的 Lisp 方言的系统,但 describe
功能未实现。这里发生了一些 XY 事情,但我也在尝试理解 Lisp 和 CL。
不支持使用 set
或 setq
定义全局变量。定义全局变量有两种常用方法:
(defparameter *par* 20) ; notice the earmuffs in the name!
(defvar *var* 30) ; notice the earmuffs in the name!
所有的全局变量都是特殊的。无法描述词法范围的变量(非特殊变量)。例如
(let ((x 10))
(describe 'x)) ; ==> X is the symbol X
它描述的不是词法变量而是符号表示。这真的无关紧要,因为您可能永远不需要在 运行 时间内知道,因为您在编写时知道它是绑定词法变量还是全局特殊变量,符合全局变量的耳罩命名约定。
许多 Common Lisp 实现在某些系统依赖包中提供函数 variable-information
。
在 SBCL 中:
* (require :sb-cltl2)
NIL
* (sb-cltl2:variable-information '*standard-output*)
:SPECIAL
NIL
((TYPE . STREAM))
这个函数是作为一些其他功能的一部分被提议包含在 ANSI CL 中的,但没有进入标准。仍然有很多实现。有关文档,请参阅:https://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node102.html
当您在其上创建闭包时,将捕获非特殊变量的环境:
(let ((x 1))
(let ((f (lambda () x)))
(let ((x 2))
(eql 2 (funcall f)))))
;;=> NIL
特殊变量的词法环境不会:
(defvar *x*) ; *x* is special
(let ((*x* 1))
(let ((f (lambda () *x*)))
(let ((*x* 2))
(eql 2 (funcall f)))))
;;=> T
使用这种方法,您可以轻松定义一个 宏,它将扩展为类似于之前的代码,让您确定一个符号是否在全球范围内被宣布为特殊符号:
(defmacro specialp (symbol)
(let ((f (gensym "FUNC-")))
`(let ((,symbol 1))
(let ((,f (lambda () ,symbol)))
(let ((,symbol 2))
(eql 2 (funcall ,f)))))))
(specialp x) ;=> NIL
(specialp *x*) ;=> T
请注意,这不是函数,而是宏。这意味着使用 符号 X 和 *X* 调用 specialp 的宏函数。这很重要,因为我们必须构建使用这些符号的代码。你不能用一个函数来做到这一点,因为没有(可移植的)方法来获取一个符号并创建一个词法环境,该词法环境具有一个具有该名称的词法变量和一个引用它的 lambda 函数。
如果您尝试将它与某些符号一起使用,这也会带来一些风险。例如,在 SBCL 中,如果您尝试将 *standard-output* 绑定到不是流或流指示符的内容,您将收到错误消息:
CL-USER> (specialp *standard-output*)
; in: SPECIALP *STANDARD-OUTPUT*
; (LET ((*STANDARD-OUTPUT* 1))
; (LET ((#:FUNC-1038 (LAMBDA # *STANDARD-OUTPUT*)))
; (LET ((*STANDARD-OUTPUT* 2))
; (EQL 2 (FUNCALL #:FUNC-1038)))))
;
; caught WARNING:
; Constant 1 conflicts with its asserted type STREAM.
; See also:
; The SBCL Manual, Node "Handling of Types"
;
; compilation unit finished
; caught 1 WARNING condition
我认为在 运行 时间*获取此信息的唯一方法是使用 CL 的扩展,如 Rainer 指出的那样,或者使用 eval
。
(defun specialp (x)
(or (boundp x)
(eval `(let (,x)
(declare (ignorable ,x))
(boundp ',x)))))
(缺陷警告:如果变量未绑定但声明为与 nil 不兼容的类型,这可能会引发错误。感谢 Joshua 在他的回答中指出。)
* 宏方法确定它在宏扩展时检查哪个符号,以及该符号在编译时是词法的还是特殊的。这对于检查 repl 中变量的状态很好。如果你想,例如打印包导出的所有特殊变量,但是,您会发现要使用宏版本,您最终必须在调用站点使用 eval
:
(loop for s being the external-symbols of :cl-ppcre
when (eval `(specialp-macro ,s)) do (print s))