无法使用make-symbol生成的名称调用宏中定义的函数

jjp*_*jpe 6 lisp emacs macros elisp

我正在尝试编写一个ELisp宏来根据一些常见数据生成多个函数.例如,当我想要计算fn名称时,我会写出类似的内容(我暂时忽略了卫生,我将符号文字传递给宏,因此评估无关紧要):

(cl-defmacro def-fns (sym)
  "SYM."
  (let ((s1 (make-symbol (concat (symbol-name sym) "-1")))
        (s2 (make-symbol (concat (symbol-name sym) "-2"))))
    `(progn (defun ,s1 () (+ 1 2 3))
            (defun ,s2 () "six"))))
Run Code Online (Sandbox Code Playgroud)

我希望在调用时生成2个fns,调用foo-1foo-2.

然后我应该能够像这样调用宏和fns:

(def-fns foo)
(foo-1)
;; => 6
(foo-2)
;; -> "six
Run Code Online (Sandbox Code Playgroud)

甚至(def-fns foo)Emacs 的宏观扩张也表明情况应该是这样的:

(progn
  (defun foo-1 nil (+ 1 2 3))
  (defun foo-2 nil "six"))
Run Code Online (Sandbox Code Playgroud)

然而,当我评价的def-fns定义和调用它它不会产生这些功能.为什么会这样?这种技术适用于Common Lisp和Clojure(它们具有非常相似的宏系统),那么为什么不在ELisp?

sds*_*sds 9

您的代码也无法在CL中运行.

问题在于make-symbol- 它创建了一个新的符号,所以

(eq (make-symbol "A") (make-symbol "A"))
==> nil
Run Code Online (Sandbox Code Playgroud)

这意味着您的宏创建了函数,但将它们绑定到您不再拥有句柄的符号.

在您评估时(foo-1),Emacs Lisp阅读器会尝试查找实习符号的功能绑定foo-1,而不是宏创建的新未处理符号.

您需要使用intern:它使符号"通常可用",可以这么说:

(eq (intern "a") (intern "a))
==> t
Run Code Online (Sandbox Code Playgroud)

因此,更正后的代码如下所示:

(defmacro def-fns (sym)
  "SYM."
  (let ((s1 (intern (concat (symbol-name sym) "-1")))
        (s2 (intern (concat (symbol-name sym) "-2"))))
    `(progn (defun ,s1 () (+ 1 2 3))
            (defun ,s2 () "six"))))
(def-fns foo)
(foo-1)
==> 6
(foo-2)
==> "six"
Run Code Online (Sandbox Code Playgroud)

备注:

  1. 如果您使用的是CL,那么未打印的符号就会被打印出来,#:foo-1并且问题的根源对您来说是显而易见的.
  2. 真的需要使用它是非常罕见的.通常,您要使用或者.make-symbolinterngensym