Making a function that defines functions in GNU Emacs ELisp
Suppose that for some reason you're trying to create a number of
functions that follow a fixed template; for example, they should
all be called 'mh-visit-<name>' that will all use mh-visit-folder
to visit the (N)MH folder
'+inbox/<name>'. In my last installment
I did this with an Emacs Lisp macro, but
it turns out there are reasons to prefer a function over a macro.
For example, you apparently can't use a macro in
which means you have to write all of the macro invocations for all
of the functions you want to create by hand, instead of having a
list of all of the names and dolist'ing over it.
There are two ways to write this function to create functions, a simpler version that doesn't necessarily work and a more complicated version that always does (as far as I know). I'll start with the simple version and describe the problem:
(defun make-visit-func (fname) (let ((folder-name (concat "+inbox/" fname)) (func (concat "mh-visit-" fname))) (defalias (intern func) (lambda () (interactive) (mh-visit-folder folder-name)) (format "Visit MH folder %s." folder-name))))
If you try this in an Emacs *scratch* buffer, it may well work. If you put this into a .el file (one that has no special adornment) and use it to create a bunch of functions in that file, then try to use one of them, Emacs will tell you 'mh-visit-<name>: Symbol’s value as variable is void: folder-name'. This is because folder-name is dynamically scoped, and so is not captured by the lambda we've created here; the 'folder-name' in the lambda is just a free-floating variable. As far as I know, there is no way to create a lexically bound variable and a closure without making all elisp code in the file use lexical binding instead of dynamic scoping.
(As of Emacs 29.1, .el files that aren't specifically annotated on their first lines still use dynamic scoping, so your personal .el files are probably set up this way. If you innocently create a .el file and start pushing code from your .emacs into it, dynamic scoping is what you get.)
(defun make-visit-func (fname) (let ((folder-name (concat "+inbox/" fname)) (func (concat "mh-visit-" fname))) (apply `(defalias ,(intern func) (lambda () ,(format "Visit MH folder %s." folder-name) (interactive) (mh-visit-folder ,folder-name))))))
(This time I've attached the docstring to the lambda, not the alias, which is really the right thing but which seems to be hard in the other version.)
As I understand it, we are effectively doing what a macro would be;
we are creating the S-expression version of the function we want,
created variables being directly spliced in by value, not by their
(dynamically bound) names.
PS: I probably should switch my .el files over to lexical binding and fix anything that breaks, especially since I only have one right now. But the whole thing irritates me a bit. And I think the apply-based version is still a tiny bit more efficient and better, since it directly substitutes in the values (and puts the docstring on the lambda).
Comments on this page:Written on 18 September 2023.