【问题标题】:Uninterned symbols symbols非驻留符号symbol
【发布时间】:2012-12-08 20:27:14
【问题描述】:

Common lisp 有一些我不明白的地方。

假设我正在编写一个类似这样的宏:

(defmacro test-macro () 
   (let ((result (gensym))) 
      `(let ((,result 1))
         (print (incf ,result))))) 

我做不到

> (test-macro)
2
2

现在我想看看它是如何扩展的

> (macroexpand-1 '(test-macro))
(LET ((#:G4315 1)) (PRINT (INCF #:G4315))) ;
T

好的。 gensym 生成了一些独特的符号,这些符号被打印为 uninterned。

据我所知,uninterned 符号是评估器不会在内部为其创建符号数据绑定的符号。

因此,如果我们将宏扩展为该表单,则 (incf #:G4315) 上应该会出现错误。 为了测试这一点,我们可以在 REPL 中评估该表单:

> (LET ((#:G4315 1)) (PRINT (INCF #:G4315)))
*** - SETQ: variable #:G4315 has no value

那么为什么扩展成这个字符串的宏可以完美运行而表单本身却不行呢?

【问题讨论】:

    标签: lisp common-lisp symbols gensym


    【解决方案1】:

    符号可以被嵌入到包中,也可以不被嵌入。 被嵌入到包中的符号可以被查找和找到。无法在包中查找 uninterned 符号。一个包中只能有一个特定名称的符号。只有一个符号CL-USER::FRED

    你写

    据我所知,uninterned 符号是评估器不会在内部为其创建符号数据绑定的符号。

    错了。 Uninterned 符号是在任何包中都没有interned 的符号。否则他们完全没问题。 interned 表示 已在包的 registry 中为其符号注册。

    s 表达式readerreader 期间确实使用符号名称和包来标识符号。如果没有这样的符号,它就会被拘留。如果有,则返回这个。

    读者确实会在当前包中按名称查找符号:

     (read-from-string "FOO") -> symbol `FOO`
    

    第二次:

     (read-from-string "FOO") -> symbol `FOO`
    

    它总是相同的符号FOO

     (eq (read-from-string "FOO") (read-from-string "FOO"))  -> T
    

    #:FOO 是名称为FOO 的非内部符号的语法。它没有在任何包中实习。如果读者看到这种语法,它会创建一个新的非内部符号。

     (read-from-string "#:FOO") -> new symbol `FOO`
    

    第二次:

     (read-from-string "#:FOO") -> new symbol `FOO`
    

    两个符号不同。它们具有相同的名称,但它们是不同的数据对象。除了包之外,没有其他符号注册表。

     (eq (read-from-string "#:FOO") (read-from-string "#:FOO"))  -> NIL
    

    因此在您的情况(LET ((#:G4315 1)) (PRINT (INCF #:G4315))) 中,非驻留符号是不同的对象。第二个是一个不同的变量。

    Common Lisp 有一种打印数据的方法,以便在打印/读取过程中保留身份

    CL-USER 59 > (macroexpand-1 '(test-macro))
    (LET ((#:G1996 1)) (PRINT (INCF #:G1996)))
    T
    
    CL-USER 60 > (setf *print-circle* t)
    T
    
    CL-USER 61 > (macroexpand-1 '(test-macro))
    (LET ((#1=#:G1998 1)) (PRINT (INCF #1#)))
    T
    

    现在您看到打印的 s 表达式的第一个符号有一个标签 #1=。然后它稍后引用相同的变量。这可以被读回并保留符号标识 - 即使 阅读器 无法通过查看包来识别符号。

    因此,宏创建了一个表单,其中只生成了一个符号。当我们打印该表单并想要读回它时,我们需要确保保留未留存符号的身份。将*print-circle* 设置为T 进行打印有助于做到这一点。

    问:为什么我们使用GENSYMgenerate symbol)在宏中使用uninterned生成符号?

    这样我们就可以拥有独特的新符号,它们不会与代码中的其他符号发生冲突。他们通过函数gensym 获得一个名字——通常在末尾有一个计数。由于它们是新的符号,没有被任何包中,所以不会有任何命名冲突。

    CL-USER 66 > (gensym)
    #:G1999
    
    CL-USER 67 > (gensym)
    #:G2000
    
    CL-USER 68 > (gensym "VAR")
    #:VAR2001
    
    CL-USER 69 > (gensym "PERSON")
    #:PERSON2002
    
    CL-USER 70 > (gensym)
    #:G2003
    
    CL-USER 71 > (describe *)
    
    #:G2003 is a SYMBOL
    NAME          "G2003"
    VALUE         #<unbound value>
    FUNCTION      #<unbound function>
    PLIST         NIL
    PACKAGE       NIL                      <------- no package
    

    【讨论】:

    • 如果我正确理解了您的解释,gensym 仍然可以正常工作,即使它没有在符号名称中添加计数的数字,即如果它返回一个同名的 uninterned 符号每次调用它(使用相同的参数)。那是对的吗?如果是这样:为什么要添加数字?这样可以更轻松地分辨出哪些符号是相同的,哪些不在macro-expand 的输出中?
    • @sepp2k: 正确,这个数字只是为了便于发现未留存符号不同的地方和可能相同的地方。这是一个调试帮助。在早期的 Lisp 方言(没有包)中,它可能更重要。
    • 非常感谢。 print-circle 的解释对理解它的工作原理很有帮助。也感谢您对未处理符号的一些解释。
    • 介意将“then the packages”改为“than the packages”吗?
    【解决方案2】:

    gensym 生成一个符号,当您打印它时,您会得到该符号的“字符串”表示,它与“阅读器”表示不同,即符号的代码表示。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-04-26
      • 1970-01-01
      • 2014-05-08
      • 2015-02-17
      • 2010-09-27
      • 1970-01-01
      • 2015-11-05
      相关资源
      最近更新 更多