9

我正在尝试在 emacs lisp 中编写一个宏来创建一些“辅助函数”。</p>

最终,我的辅助函数将比我这里的更有用。我意识到可能有更好/更直观的方法来完成同样的事情(请发布),但我的基本问题是为什么这不起作用/我做错了什么:

(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?")

如果我获取宏扩展的输出并对其进行评估,我将获得我打算通过宏获得的交互式函数,但即使宏运行并且似乎正在评估,我也无法调用M-x text-nior text-swallow

4

4 回答 4

12

这可以满足您的要求:

(defmacro deftext (functionname texttoinsert)
  (let ((funsymbol (intern (concat "text-" functionname))))
`(defun ,funsymbol () (interactive) (insert-string ,texttoinsert))))
于 2009-03-19T04:11:50.623 回答
4

正如已经指出的那样,解决方案是使用intern而不是make-symbol.

可以创建多个具有相同名称的独立符号,但其中只有一个可以是给定名称的规范符号 - 即当您在其他地方引用它时将获得的符号。

intern返回给定名称的规范符号。当该名称的内部符号不存在时,它才会创建一个新符号。这意味着它只会为任何给定名称1创建一个符号。

make-symbol另一方面,每次调用它时都会创建一个新符号。这些是非驻留符号——每一个都是完全有效和功能性的符号,但不是当您通过名称引用符号时会看到的符号。

看:C-hig (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."))

所有这一切的棘手部分 - 以及评估扩展形式的原因是你想要的,但宏没有 - 与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)); 并且该反引号表达式将在扩展时进行评估,以创建一个新的uninterned符号,该符号将成为该扩展创建的对象的一部分。

在我们之前的示例中,生成的对象有一个 car defun(interned)和一个 cadr of foo1or foo2(两者都被 interned)。

在最后一个示例中,对象有一个defun(interned) 的 car,但有一个uninterned符号的 cadr(名称由concatenate表达式产生)。

最后,如果您打印该对象,则该非实习函数符号的打印表示将是符号名称,并且通过评估它来读取该打印表示将导致定义规范符号的函数单元格。

1其实这个unintern函数可以用来unintern一个符号,之后调用intern同名自然创建一个新符号;但这对于本次讨论并不重要。

于 2014-01-28T02:51:19.717 回答
3

FWIW,如果您使用lexical-binding,则不需要使用宏:

(defun deftext (functionname texttoinsert)
  (defalias (intern (concat "text-" functionname))
    (lambda ()
      (interactive)
      (insert-string texttoinsert))))
于 2014-01-28T00:00:53.157 回答
1

已经好几年了,但我认为您可能缺少fset定义函数的方法;如果您也想要编译时间,请参阅文档。

于 2009-03-19T00:18:43.803 回答