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

Common lisp 在CommonLisp中,如何测试变量是否特殊?,common-lisp,Common Lisp,我想我可以通过谷歌或者我正在读的书找到这个,但事实证明这是难以捉摸的 在我正在学习的实现中,我可以在顶层执行以下操作: (defvar*foo*4) (设置栏3) 如果我随后调用(descripe'*foo*)和(descripe'bar),我会得到一个说明,说明*foo*是特殊的,bar是非特殊的(以及其他细节) 是否有一个函数将符号变量作为参数,如果它是特殊的,则返回true或false?如果是这样的话,descripe是否可能部分通过调用它来实现 上下文:我正在学习Common Lisp,

我想我可以通过谷歌或者我正在读的书找到这个,但事实证明这是难以捉摸的

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

(defvar*foo*4)
(设置栏3)

如果我随后调用
(descripe'*foo*)
(descripe'bar)
,我会得到一个说明,说明
*foo*
是特殊的,
bar
是非特殊的(以及其他细节)

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


上下文:我正在学习Common Lisp,但在工作中我有一个类似Common Lisp的Lisp方言系统,但
description
函数尚未实现。这里有一种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

它描述的不是词汇变量,而是符号表示。这其实并不重要,因为您可能永远都不需要在运行时知道,因为您在编写时知道它是绑定词汇变量还是全局特殊变量,因为它符合全局变量的耳罩命名约定。

许多常见的Lisp实现在某些系统中提供函数
变量信息
依赖包

在SBCL:

* (require :sb-cltl2)
NIL

* (sb-cltl2:variable-information '*standard-output*)
:SPECIAL
NIL
((TYPE . STREAM))

该功能被提议作为其他一些功能的一部分包含在ANSI CL中,但没有被纳入标准中。仍然有许多实现有它。有关文档,请参见:

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

(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
请注意,这不是一个函数,而是一个宏。这意味着specialp的宏函数被调用时使用符号X*X*。这很重要,因为我们必须构造使用这些符号的代码。你不能用一个函数来实现这一点,因为没有(可移植的)方法来获取一个符号并创建一个词法环境,该环境有一个同名的词法变量和一个引用它的lambda函数

如果您尝试将其与某些符号一起使用,这也有一些风险。例如,在SBCL中,如果您尝试将(例如,*标准输出*绑定到非流或流指示符的对象,您将得到一个错误:

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))

“不支持使用set或setq定义全局变量”setq不定义全局变量,但使用符号定义得很好。它只设置符号的值单元格。不要求符号是全局变量。我将
foo
更改为
*foo*
,以便更清楚地理解其意图。我知道使用耳罩有助于澄清程序员的意图,但[我认为]这对编译器来说并不重要。我正在关注“不可能描述词汇范围内的变量(非特殊变量)”和您提供的示例。似乎我可以从中获得很多见解。@brian_o词汇变量在运行时并不存在;它们不是物体。它们不是可以传递给函数的东西。当您计算
(foo-bar)
时,系统调用名为
foo
的函数,并使用
bar
的值调用它。当您计算
(foo'bar)
时,系统调用名为
foo
的函数,其值为
'bar
,即符号
bar
。不过,您可以做的是确定一个符号是否被全局声明为特殊符号。
gensym
是否在其中,以便
specialp
在您传递它时不会爆炸?@brian\u o是的。我想您也可以使用本地定义的函数(例如,使用flet),并完全避免这个问题,但通常更好的做法是避免绑定可能绑定到其他地方的名称。这非常聪明,但我仍然认为在编写代码时应该知道这一点:-)