Wandering Thoughts archives

2023-09-18

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 dolist, 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.)

Fortunately we can use a giant hammer, basically imitating the structure of our macro version and directly calling apply. This version looks like this:

(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, with our let 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).

programming/EmacsFunctionDefiningFunction written at 22:02:24; Add Comment


Page tools: See As Normal.
Search:
Login: Password:
Atom Syndication: Recent Pages, Recent Comments.

This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.