Can't call functions defined in macro with nam

2020-04-06 05:59发布

问题:

I'm trying to write an ELisp macro to generate a multiple functions based on some common data. For example, when I want to compute the fn names I write something like (I'm ignoring hygiene for the moment, I'm passing a symbol literal into the macro so evaluation shouldn't matter):

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

which I expect to generate 2 fns when invoked, called foo-1 and foo-2.

I should then be able to invoke the macro and fns like so:

(def-fns foo)
(foo-1)
;; => 6
(foo-2)
;; -> "six

Even the macroexpansion of (def-fns foo) in Emacs suggests that this should be the case:

(progn
  (defun foo-1 nil (+ 1 2 3))
  (defun foo-2 nil "six"))

However, when I evaluate the def-fns definition and invoke it it does not generate those functions. Why is this the case? This technique works in Common Lisp and in Clojure (which have very similar macro systems), so why not in ELisp?

回答1:

Your code would not work in CL either.

The problem is with make-symbol - it creates a new symbol, so that

(eq (make-symbol "A") (make-symbol "A"))
==> nil

This means that your macro creates the functions but binds them to symbols which you no longer have a handle on.

When you evaluate (foo-1), Emacs Lisp reader tries to find the function binding of the interned symbol foo-1, not the fresh uninterned symbol your macro created.

You need to use intern instead: it makes the symbol "generally available", so to speak:

(eq (intern "a") (intern "a))
==> t

So, the corrected code looks like this:

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

Notes:

  1. If you were using CL, the uninterned symbols would have been printed as #:foo-1 and the source of your problem would have been obvious to you.
  2. It is exceedingly rare that you really need to use make-symbol. Usually, you want to use either intern or gensym.