Sharpsign Colon 和 Gensym 的区别

8

我刚刚阅读了关于#:阅读宏的内容,听起来它和gensym有非常相似的作用。

#::"引入一个未注册的符号"

gensym:"创建并返回一个新的、未注册的符号"

因此,进行一个简单的测试:

CL-USER> #:dave
; Evaluation aborted on #<UNBOUND-VARIABLE DAVE {1002FF77D3}>.
CL-USER> (defparameter #:dave 1)
#:DAVE
CL-USER> #:dave
; Evaluation aborted on #<UNBOUND-VARIABLE DAVE {100324B493}>.

很好,这样做是失败的,正如应该的那样。
现在进行宏测试。
(defmacro test (x)
  (let ((blah '#:jim))
    `(let ((,blah ,x))
       (print ,blah))))

CL-USER> (test 10)

10 
10
CL-USER>

这个东西很甜,可以像gensym一样使用。

对我来说,这看起来比gensym更清洁,结果似乎相同。我确定我错过了一个重要的细节,所以我的问题是,它是什么?

2个回答

9
每次展开宏时,它都会使用相同的符号。
(defmacro foo () `(quote #:x))
(defmacro bar () `(quote ,(gensym)))

(eq (foo) (foo)) => t
(eq (bar) (bar)) => nil

每次评估Gensym都会创建一个新的符号,但是Sharp Colon只有在读取时才会创建一个新的符号。 虽然使用Sharp Colon不太可能引起问题,但有几种罕见情况下使用它会导致几乎无法找到的错误。始终使用gensym更安全。 如果你想使用类似Sharp Colon的东西,你应该看看Let Over Lambda中的defmacro!宏。

做到了,我之前没想到使用捕获,真是太傻了。我甚至读过《Let Over Lambda》,今天真的有点糊涂呢,呵呵。 - Baggers

7
GENSYM 类似于 MAKE-SYMBOL,不同的是 GENSYM 通过计数来支持炫酷的命名 - 因此符号具有唯一的名称,在例如宏扩展中使用 gensyms 时使调试更容易一些。 #:foo 是读取器的一种表示法。
因此你有一个创建这些内容的函数和文字表示法。请注意,当 *print-circle* 为真时,s-表达式中可能会保留某种标识:#(#1=#:FOO #1#)
通常,这类似于 (a . b)(cons 'a 'b)#(a b)(vector 'a 'b)...其中一个是文字数据,而另一个是将创建('cons')新的对象的形式。
如果您查看您的宏,主要问题在于嵌套使用可能会导致问题。无论是在词法上还是动态上。
  • 在词法上,它可能是相同的变量,被重新绑定了。
  • 动态地,如果它是一个特殊变量,它也可能被重新绑定。
在宏扩展时使用生成的符号将确保扩展后的代码与其他代码不共享绑定。

哦,是的,我没有花时间考虑捕获的影响,这显然是整个重点..糟糕!谢谢你提醒我。 - Baggers

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接