【问题标题】:In Common Lisp, how to test if variable is special?在 Common Lisp 中,如何测试变量是否特殊?
【发布时间】:2015-04-04 09:02:42
【问题描述】:

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

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

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

如果我随后调用 (describe '*foo*)(describe 'bar),我会得到一个描述,说 *foo* 是特殊的,bar 是非特殊的(以及其他详细信息)。

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

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

【问题讨论】:

    标签: common-lisp


    【解决方案1】:

    我相信在运行时获取此信息的唯一方法* 是使用 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))
    

    【讨论】:

      【解决方案2】:

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

      (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
      

      请注意,这不是函数,而是宏。这意味着使用 symbols 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
      

      【讨论】:

      • gensym 是否在里面,这样specialp 不会在你通过f 时爆炸?
      • @brian_o 是的。我想您也可以使用本地定义的函数(例如,使用 flet),并完全避免该问题,但通常更好的做法是避免绑定可能在其他地方绑定的名称。跨度>
      • 这非常聪明,但我仍然认为应该在代码时知道这一点:-)
      【解决方案3】:

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

      【讨论】:

        【解决方案4】:

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

        (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
        

        它描述的不是词法变量而是符号表示。这真的没关系,因为您可能永远不需要在运行时知道,因为您在编写时就知道它是绑定词法变量还是全局特殊变量,符合全局变量的耳罩命名约定。

        【讨论】:

        • "不支持使用 set 或 setq 定义全局变量" Setq 没有定义全局变量,但是使用带有符号的 set 定义得很好。它只是设置符号的值单元格。符号不需要是全局变量。
        • 我把foo改成了*foo*,这样意图就更清楚了。我知道使用耳罩有助于阐明程序员的意图,但[我认为]这对编译器并不重要。
        • 我盯着“无法描述词法范围的变量(非特殊)”和您提供的示例。看来我可能会从中得到很多见解。
        • @brian_o 词法变量在运行时并不真正存在;它们不是对象。它们不是你可以传递给函数的东西。当您评估(foo bar) 时,系统会调用foo 命名的函数,并使用bar 调用它。计算(foo 'bar)时,系统调用foo命名的函数,其值为'bar,即符号bar。不过,您可以做的是确定一个符号是否被全局声明为特殊的。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-03-14
        • 1970-01-01
        • 2013-06-12
        • 1970-01-01
        • 2013-08-08
        • 2011-02-27
        相关资源
        最近更新 更多