正如所指出的那样,解决方案是使用 `intern` 而不是 `make-symbol`。
可以创建多个具有相同名称的独立符号,但其中只有一个可以成为给定名称的规范符号——即在其他地方引用时将获得的符号。
`intern` 返回给定名称的规范符号。仅当不存在该名称的已内部化符号时,它才会创建新符号。这意味着它只会为任何给定名称创建一个符号。
另一方面,`make-symbol` 每次调用时都会创建一个新符号。这些是未内部化的符号——每个符号都是完全有效和可用的符号,但不是通过其名称引用时将看到的符号。
请参见:`C-h i g (elisp) Creating Symbols`
因为 `defun` 返回它设置的符号,所以您可以通过捕获返回值并将其用作函数来观察正在发生的情况:
(defalias 'foo (deftext "ni" "What is the flight speed velocity of a laden swallow?"))
M-x text-ni
M-x foo
或类似:
(call-interactively (deftext "shrubbery" "It is a good shrubbery. I like the laurels particularly."))
在所有这些中最棘手的部分——也是评估扩展形式做你想要的事情,但宏没有做到的原因——与Lisp阅读器如何以及何时(或者是否)将函数名称转换为符号有关。
如果我们编写一个名为foo1的函数:
(defun foo1 (texttoinsert) (insert-string texttoinsert))
lisp阅读器将其作为文本读取,并将其转换为lisp对象。我们可以使用read-from-string
做同样的事情,然后我们可以查看生成的lisp对象的打印表示:
ELISP> (car (read-from-string "(defun foo1 (texttoinsert) (insert-string texttoinsert))"))
(defun foo1
(texttoinsert)
(insert-string texttoinsert))
在这个对象中,函数名是具有名称“foo1”的规范符号。请注意,我们从
read-from-string
看到的返回值仅为该对象的
打印表示,规范符号仅由其名称表示。打印表示不能使我们区分已内部化和未内部化的符号,因为所有符号都仅由其名称表示。
(暂时跳过,当评估宏的
打印形式时,这是您遇到问题的源头,因为该打印形式通过lisp读取器传递,曾经是未内部化的符号成为内部化的符号。)
如果我们继续讨论宏:
(defmacro deffoo2 ()
`(defun foo2 (texttoinsert) (insert-string texttoinsert)))
ELISP> (car (read-from-string "(defmacro deffoo2 ()
`(defun foo2 (texttoinsert) (insert-string texttoinsert)))"))
(defmacro deffoo2 nil
`(defun foo2
(texttoinsert)
(insert-string texttoinsert)))
这次读者已经将宏定义读成一个lisp对象,在该对象中有一个规范符号foo2
。我们可以直接检查这个对象来验证:
ELISP> (eq 'foo2
(cadr (cadr (nth 3
(car (read-from-string "(defmacro deffoo2 ()
`(defun foo2 () (insert-string texttoinsert)))"))))))
t
所以对于这个宏来说,它已经在任何宏调用/扩展发生之前处理了那个规范符号
foo2
,因为当读取宏自身时,lisp读取器已经建立了这种关系。与我们之前简单的函数定义不同(其中函数符号由Lisp读取器确定并定义),当调用和扩展宏时,不会使用Lisp读取器。宏展开是使用预先存在的Lisp对象执行的 - 无需进行读取操作。
在此示例中,函数符号已经出现在宏中,因为它是规范符号,我们可以在代码的其他位置使用
(foo2)
来调用该函数。(或者,如果我使定义交互式,可以使用
M-x foo2
。)
最后回到原始问题中的宏,很明显lisp读取器永远不会遇到将定义函数名称符号:
ELISP> (car (read-from-string "(defmacro deftext (functionname texttoinsert)
`(defun ,(make-symbol (concatenate 'string \"text-\" functionname)) ()
(interactive)
(insert-string ,texttoinsert)))"))
(defmacro deftext
(functionname texttoinsert)
`(defun ,(make-symbol
(concatenate 'string "text-" functionname))
nil
(interactive)
(insert-string ,texttoinsert)))
相反,由lisp阅读器产生的对象包含表达式
,(make-symbol (concatenate 'string "text-" functionname))
;并且该反引号表达式将在扩展时评估,以创建一个新的
未注册符号,该符号将成为由该扩展创建的对象的一部分。
在我们之前的示例中,生成的对象具有
defun
(已注册)的car和
foo1
或
foo2
(两者都已注册)的cadr。
在这个最后的例子中,对象具有
defun
(已注册)的car,但是一个
未注册符号的cadr(名称来自
concatenate
表达式的结果)。
最后,如果您
打印该对象,则未注册函数符号的打印表示形式将是符号名称,并且通过评估它来
读取该打印表示形式会导致定义
规范符号的函数单元格。
1实际上,可以使用
unintern
函数取消注册符号,此后对于相同名称调用
intern
自然会创建一个新符号; 但是,这对于本讨论并不重要。