gensym和Uninterned

当Reader读取到一个Symbol时,Symbol会被转成大写,然后内部会调用INTERN函数去创建新的Symbol。

当Reader遇到“#:”开头的Symbol时,不会调用INTERN函数,所以这个符号不会添加到当前Package里。以“#:”打头的符叫Uninterned Symbol,所以等于每次在REPL输入“#:”的符号时,都等同创建个新的Symbol:

CL-USER> (eq '#:x '#:x)
NIL

有一个让人疑惑的是gensym,写宏时时常会用到gensym来防止Symbol冲突:

(defmacro foo ()
  (let ((var (gensym)))
    (let ((,var 1))
       (print ,var))))

执行这段宏:

CL-USER> (foo)
1

预料之中,可以正常输出“1”。再看看foo这个宏展开后的样子:

CL-USER> (macroexpand-1 '(foo))
(LET ((#:G1075 1))
  (PRINT #:G1075))
T

可以看到gensym生成了以“#:”打头Symbol,#:G1075是Uninterned Symbols,但为什么在宏里能执行呢?如果把这段宏展开后的代码直接粘贴到REPL里执行一下,REPL会提示:The variable #:G1075 is unbound:

CL-USER> (LET ((#:G1075 1))
	   (PRINT #:G1075))
T
The variable #:G1075 is unbound

这是意料之中的,**因为你拷贝过去的代码中的Symbol直接是由Reader读取的**,Reader读到#:G1075时,它成为一个Uninterned Symbols——这个Symbol不会注册到Package里的,所以PRINT的时候是不能打印的。

而宏展开后的并不是表面看到的那样,在分析前,我们首先要改一个变量:

(setf *print-circle* t)

然后再看看展开后的模样:

CL-USER> (macroexpand-1 '(foo))
(LET ((#1=#:G1076 1))
  (PRINT #1#))
T

看到了吧,这次展开后看到的Symbol跟之前是不一样的格式。gensym保证了生成的Symbol名是唯一的,因为内部会处理好的,你就当它是内部变量吧,所以你直接用“#:”开头Symbol是不行的:

CL-USER> (defvar #:x 1)
#:X
CL-USER> #:x
The variable #:X is unbound.

再看看(defvar #:x 1)的宏展开:

CL-USER> (macroexpand-1 '(defvar #:x 1))
(PROGN
  (EVAL-WHEN (:COMPILE-TOPLEVEL) (SB-IMPL::%COMPILER-DEFVAR '#1=#:X))
  (EVAL-WHEN (:LOAD-TOPLEVEL :EXECUTE)
    (SB-IMPL::%DEFVAR '#1# (UNLESS (BOUNDP '#1#) 1) 'T NIL 'NIL
		      (SB-C:SOURCE-LOCATION))))
T

这说明Lisp内部也不是使用的“#:”打头的Symbol名,而是用#1#直接引用的这Symbol。比如:

CL-USER> (eq '#1=#:G2052 '#1#)
T

所以,内部会保证这些符号的唯一性。