在Common Lisp中,如何测试变量是否特殊?

时间:2015-02-04 17:59:49

标签: common-lisp

我以为我可以通过Google,SO或我正在阅读的书籍找到这个,但事实证明这是难以捉摸的。

在我正在学习的实现中,我可以在顶层执行以下操作:

(defvar *foo* 4) (set 'bar 3)

如果我再拨打(describe '*foo*)(describe 'bar),我会得到一条说明,*foo*是特殊的,bar是非特殊的(以及其他详细信息)。

是否有一个函数将符号变量作为参数并返回true或false如果它是特殊的?如果是这样,describe可能部分通过调用来实现吗?

上下文:我正在学习Common Lisp,但是在工作中我有一个类似于Common Lisp的Lisp方言系统,但是describe函数没有实现。这里有一些XY事情,但我也试图找到Lisp和CL。

4 个答案:

答案 0 :(得分:10)

许多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

答案 1 :(得分:5)

当您在其上创建闭包时,将捕获非特殊变量的环境:

(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

答案 2 :(得分:1)

不支持使用setsetq定义全局变量。定义全局变量有两种常用方法:

(defparameter *par* 20) ; notice the earmuffs in the name!
(defvar *var* 30)       ; notice the earmuffs in the name!

所有全局变量都很特殊。词汇范围的变量(不是特殊的)不可能被描述。 E.g。

(let ((x 10))
  (describe 'x)) ; ==> X is the symbol X

它不是描述词法变量而是描述符号表示。这真的无关紧要,因为你可能永远不需要在运行时知道,因为当你通过符合全球耳罩命名约定来限制词汇变量或全局特殊时你就会知道这个。变量

答案 3 :(得分:1)

我认为在运行时*获取此信息的唯一方法是使用Raner指出的CL扩展,或者使用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))