我正在尝试在emacs lisp中编写一个宏来创建一些“辅助函数”。
最终,我的助手功能将比我在这里更有用。我意识到可能有更好/更直观的方法来完成同样的事情(请发帖)但我的基本问题是为什么这不起作用/我做错了什么:
(defmacro deftext (functionname texttoinsert)
`(defun ,(make-symbol (concatenate 'string "text-" functionname)) ()
(interactive)
(insert-string ,texttoinsert)))
(deftext "swallow" "What is the flight speed velocity of a laden swallow?")
(deftext "ni" "What is the flight speed velocity of a laden swallow?")
如果我获取macroexpand的输出并对其进行评估,我会得到我打算用宏获取的交互式函数,但即使宏运行并且似乎要评估,我也无法调用M-x text-ni
或text-swallow
。
答案 0 :(得分:10)
这样做你想要的:
(defmacro deftext (functionname texttoinsert)
(let ((funsymbol (intern (concat "text-" functionname))))
`(defun ,funsymbol () (interactive) (insert-string ,texttoinsert))))
答案 1 :(得分:4)
正如已经指出的那样,解决方案是使用intern
代替make-symbol
。
可以创建多个具有相同名称的独立符号,但只有其中一个可以是给定名称的规范符号 - 即在其他地方引用时将获得的符号
intern
返回给定名称的规范符号。如果没有该名称的实习符号已存在,则会创建一个新符号 。这意味着它只会为任何给定名称 1 创建一个符号。
make-symbol
每次调用时都会创建一个新符号。这些是 uninterned 符号 - 每个符号都是完全有效且功能符号,但不是当您通过名称引用符号时将看到的符号。
参见: C-h i g (elisp) Creating Symbols
RET
因为defun
返回它设置的符号,你可以通过捕获返回值并将其用作函数来观察正在发生的事情:
(defalias 'foo (deftext "ni" "What is the flight speed velocity of a laden swallow?"))
M-x text-ni ;; doesn't work
M-x foo ;; works
或类似地:
(call-interactively (deftext "shrubbery" "It is a good shrubbery. I like the laurels particularly."))
所有这一切中棘手的部分 - 以及评估扩展形式做了你想要的,而宏没有做到的原因 - 与时的确切方式和有关(或者确实 if )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 reader 从未遇到过它将定义的函数的函数名称符号:
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))
;并且在扩展时评估反引号表达式以创建一个新的 uninterned 符号,该符号将成为该扩展创建的对象的一部分。
在我们之前的示例中,生成的对象有一辆defun
(实习),以及foo1
或foo2
(都是实习)的人员。
在最后一个示例中,对象有一个defun
(实习)的车,但是一个 uninterned 符号的cadr(名称来自concatenate
表达式)
最后,如果你打印那个对象,那个未打开的函数符号的打印表示将是符号名称,读取通过评估它将打印出来的表示导致规范符号的功能单元被定义。
1 事实上,unintern
函数可用于取消符号,然后调用intern
同名将自然创建一个新的象征;但这对于这次讨论并不重要。
答案 2 :(得分:3)
FWIW,如果您使用lexical-binding
,则无需使用宏:
(defun deftext (functionname texttoinsert)
(defalias (intern (concat "text-" functionname))
(lambda ()
(interactive)
(insert-string texttoinsert))))
答案 3 :(得分:1)
已经好几年了,但我想你可能错过fset
来定义这个功能;如果您还想要编译时间,请参阅the docs。