你能在Emacs Lisp宏中创建交互式功能吗?

时间:2009-03-18 23:55:14

标签: macros elisp

我正在尝试在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-nitext-swallow

4 个答案:

答案 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(实习),以及foo1foo2(都是实习)的人员。

在最后一个示例中,对象有一个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