Today I saw two blog postings about emacs-lisp, which is unusual given the feeds I pay attention to. I’ve been using emacs since 1985, and so over the years I’ve written more than my share of elisp. I didn’t know anything about Lisp when I started, so I learned by doing, first by just reading other code, then experimenting and adopting more and more patterns and approaches as I came to understand them. Over the years I’ve studied Lisp here and there on my own and so have improved my elisp, I think, but I’d never call myself an expert. I’ve never worked closely with anyone who enjoyed writing elisp as much as I do, so with nobody to bounce ideas off of, I’m sure there’s still much I can learn.
The first posting I saw today is about the proper handling of association lists, or alists. An alist is a list of key/value pairs, and one critical way they’re used in emacs is for the
auto-mode-alist, which indicates what editing mode a given file should be set into when it’s visited. The mode is chosen by attempting to matching each key in the alist against the name of the file being visited; when one matches, it treats the associated value as a function and executes it in the file’s buffer. Such functions usually set the editing mode of the buffer. The keys are typically regular expressions that match file suffixes.
The author of that post had been using the
aput function to replace elements of the
auto-mode-alist with the editing modes he preferred, but recent changes to emacs resulted in
aput being moved to the
assoc package, so it was no longer directly available for use in the author’s
~/.emacs startup file. The author’s search for a substitute function to use instead resulted in him getting some bad advice about how alists are handled, and his posting explains how things really work.
One obvious way to fix the problem is to simply
(require 'assoc), which would load the
assoc package and make
aput available. The author didn’t to want to do that, probably to avoid dragging in everything else the package defines. So, he instead resorted to using the
push function to prepend elements to the
auto-mode-alist to indicate his preferred editing modes. As he explains, file loading always searches the alist from head to tail, and so will always find his settings first, even if the same key patterns occur later in the list.
Another way to do it — a more fun way, perhaps — is to write your own version of
aput. The code is interesting because it requires you to pass the alist essentially by reference to the function so it can modify it. The second elisp posting I saw today was Steve Yegge’s “Emergency Elisp” tutorial, and he mentions pass-by-reference but doesn’t really say how to do it, so let’s look at a way to do that. Here’s my version of
(defun my-aput (alist key value)
(let ((al (symbol-value alist))
(cond ((null al) (set alist (list (cons key value))))
((setq cell (assoc key al)) (setcdr cell value))
(t (set alist (cons (cons key value) al))))))
This doesn’t do exactly what the real
aput does, since it doesn’t handle the case where a key with a nil value is passed, but that’s not needed for the
auto-mode-alist case. The function expects the alist, the key, and the value. As the three cases of the
cond statement show:
- if the alist is empty, set it to a list consisting only of the new key/value pair;
- if we find the key, replace its associated value with the value passed in;
- otherwise prepend the new key/value pair to the front of the alist.
The key to making this work, though, is that we don’t really pass the alist. Instead, we pass the symbol for the alist. The symbol is kind of like a reference, in that it lets us get at the value of the alist, which we do in the first line of the function via the
symbol-value function. Our first condition tests the alist to see if it’s nil; if so, we set a new alist value for the symbol. Our third condition (which always runs if the first two don’t, since it tests the value of
t, which is always true) also sets a new value for the symbol by prepending a new key/value pair onto the current alist value.
To call it, we do this:
(my-aput 'auto-mode-alist "\\.erl\\'" 'erlang-mode)
Note how we quote the
auto-mode-alist, so rather than evaluating it and passing its value, we pass just its name, or symbol, effectively giving us pass-by-reference.
Elisp is the primary reason I keep using emacs. It’s amazingly powerful. You can make it do all kinds of editing chores for you. A lot of people today tend to rely on their IDEs, and I even tried to move to Eclipse a few years ago, but I just don’t think there’s any IDE that can match the power and extensibility that elisp gives you. I’m sure I’ll get a few disagreeable comments for that remark, but they’ll almost certainly be from people who don’t know elisp.
Since I’m not an elisp expert, though, my explanation might be off in some way, and it’s probably possible to improve my code. All constructive criticism is welcomed!