2023-09-14
An important difference between intern
and make-symbol
in GNU Emacs ELisp
Suppose, not hypothetically, that for some reason you're trying to create a GNU Emacs ELisp macro that defines a function, and for your sins you don't want to directly specify the name of your new function. In my case, I want to create a bunch of functions with names of the form 'mh-visit-<something>', which all use mh-visit-folder to visit the (N)MH folder '+inbox/<something>'. Ordinary people using macros to create functions probably give the macro the full name of the new function, but here it's less annoying if I can put it together in the macro.
(I will put the summary here: unless you know what you're doing,
use intern
instead of make-symbol
in situations where you're
making up symbols, like on the fly function definitions.)
If we passed in the name of our new function to be, I believe the resulting macro would be (without niceties like docstrings):
(defmacro make-visit-func (fname folder) `(defalias ',fname (lambda () (interactive) (mh-visit-folder ,(format "+inbox/%s" folder))))) ;; usage: (make-visit-func mh-visit-fred "fred")
(You might think we would use defun
, but defun is actually a macro
itself and defalias
is the direct thing, as I understand it; see
Defining functions.)
The ` and , bits are Backquoting,
which lets us create a quoted list of
the code the macro will generate while splicing in some variables.
The peculiar ',fname
is (E)Lisp for the value of 'fname
'
spliced in to the list but quoted instead of evaluated; 'fname
'
itself is not a string but a symbol
(also),
which we have here because symbols are how functions get their
(global) names.
(We don't have to quote 'mh-visit-fred
' when we use the macro
because macro arguments are effectively automatically quoted, by
virtue of being unevaluated.)
If we're going to use the folder name to create both the full folder
and the name of our new function, we need to turn a string into a
symbol. If you quickly scan the documentation on creating symbols,
there are two plausible ELisp functions to use, intern
and make-symbol
.
The latter's name certainly sounds like what we want, and if you aren't
an ELisp expert, you might rewrite our make-visit-func
to use it, like
so:
;; This doesn't actually work, don't copy it. (defmacro make-visit-func (folder) `(defalias ',(make-symbol (format "mh-visit-%s" folder)) (lambda () (interactive) (mh-visit-folder ,(format "+inbox/%s" folder))))) ;; usage: (make-visit-func "fred")
If you do this, you will spend some time being rather confused. Your macro will
execute without errors and debugging approaches like macroexpand
will give you a result that works when evaluated. If you change to
using intern
, this will work, so you actually want:
;; This does actually work, you can copy it. (defmacro make-visit-func (folder) `(defalias ',(intern (format "mh-visit-%s" folder)) (lambda () (interactive) (mh-visit-folder ,(format "+inbox/%s" folder)))))
What I believe is happening is caused by the following innocent seeming
bit in the description of make-symbol
, emphasis mine:
This function returns a newly-allocated, uninterned symbol whose name is name (which must be a string).
Normally, the symbols that serve as the name of functions
are interned, making
them both visible for later use and kept around by ELisp garbage
collection. However, what make-symbol
gives us is an unreferenced
symbol object (with a name assigned to it), so when defalias
connects our lambda to it to make it a function, it is still not
visible or retained. As a result it disappears into a puff of smoke
afterward; even if it hasn't been literally garbage collected, there
is no reference to it.
Evaluating the result of macroexpand
worked, because it was
the (more or less) list result and when evaluated back created a
normal, interned symbol. Here it is:
(defalias 'mh-visit-fred (lambda nil (interactive) (mh-visit-folder "+inbox/fred")))
(I've split this on three lines for readability.)
Although 'mh-visit-fred
' came from 'make-symbol
', it is printed
as text (well, its name is), and when read back to evaluate, it
will be a normal interned string. This is one of the cases where
the printed representation
is not actually the read syntax
for this value. I believe that the actual neurotically correct print
syntax for 'make-symbol
's result is '#:
'. Although I believe
Emacs accepts this as read syntax, I don't know if current versions
ever print this on output unless you try very hard.
(Having gone through this once, I'm writing it down so that if I ever do this sort of thing again I won't have to re-learn this.)
PS: Since I am not an Emacs Lisp expert, it's quite possible that there are better ways to do all of this macro.