Stir-Fried Emacs Lisp

Thomas Munro <munro@ip9.org>

Wok

Here is my small collection of newbie Emacs hackery, called Wok.

My primary editor has been vi or vim for more than 15 years (that's almost half of my life), and I always thought Emacs users were a suspicious bunch of weirdo hippies*, but since taking up Lisp programming a few years ago, I've come around to the idea that programming your editor in Lisp is actually rather fun. (Fortunately we have the excellent Viper and vimpulse.el packages so that we vim users can continue to edit text using the neurons that have developed in our hands!)

One of my hacking/learning languages these days is Scheme, so when I first started looking at Emacs Lisp code I was horrified. It uses dynamic scope, lacks tail call optimisation, and is a Lisp-2 (the first two things ought really to be fixed in future Emacs versions). But I think I am beginning to see how this works: this dialect is simple, small, and good enough for its purpose - and, most importantly it's a Lisp, so you can extend it. For example, the guys behind emacs-cl have managed to implement optional lexical scope.

Roll-your-own tail call elimination

The first thing that was stopping me from writing Emacs Lisp code was the wanton destruction required when implementing iterative processes. So, in my wok-prelude.el I have a macro that transforms recursive function definitions into equivalent loops. It's obviously a teensy bit slower than an equivalent hand-written loop, with the extra assignments it does to tame the while. This only works for statically analysable tail calls, where the name used in the tail call matches the name of the function being defined (which covers almost all of the code I write where I rely on tail call elimination, but doesn't work if the function being called in tail position is called via some other alias).

Here's an example of such a function (tail call in red):

Source

(wok-defun find (predicate list)
  (cond ((null list) nil)
        ((funcall predicate (car list)) (car list))
        (t (find predicate (cdr list)))))

After macroexpansion

(defun find (predicate list)
  (let ((result-87404 nil) 
        (more-87405 t)) 
    (while more-87405 
      (setq more-87405 nil) 
      (setq result-87404 
            (progn 
              (cond ((null list) 
                     (progn nil)) 
                    ((funcall predicate (car list)) 
                     (progn (car list))) 
                    (t (progn
                         (progn
                           (setq predicate predicate)
                           (setq list (cdr list))
                           (setq more-87405 t))))))))
    result-87404))

It introduces a few unnecessary progns here and there but it works. I guess if it were smarter it would remove redundant assignments like (setq predicate predicate). Another thing that I particularly missed from Scheme is the named let, which is a very nice syntax for certain kinds of local recursion. Here's an example of an expression which evaluates to a new random number between 0 and 1 with a distribution approaching Gaussian, by using the central limit theorem (adding up lots of dice rolls):

Source

(wok-let loop ((sum 0)
       (count 20))
  (if (zerop count)
      (/ sum 20000.0)
      (loop (+ sum (random 1001)) (- count 1))))

After macroexpansion

(let ((old-function-63937 
                  (if (fboundp (quote loop)) 
                      (symbol-function (quote loop)) 
                    nil)))
  (fset (quote loop) 
        (lambda (sum count) 
          (let ((result-63938 nil) 
                (more-63939 t)) 
            (while more-63939 
              (setq more-63939 nil) 
              (setq result-63938 
                    (progn 
                      (if (zerop count) 
                          (/ sum 20000.0) 
                          (progn 
                            (setq sum (+ sum (random 1001))) 
                            (setq count (- count 1)) 
                            (setq more-63939 t)))))) 
            result-63938))) 
(unwind-protect (loop 0 20) 
  (if old-function-63937
      (fset (quote loop) old-function-63937) 
      (makunbound (quote loop)))))

Ok, there's a hideous amount of extra work being done. The ugliest bits are concerned with saving and restoring any preexisting function-binding of the name (although in this example the function call has been eliminated, the named let syntax can also be used for recursive processes where the function call remains, so I define it as a real function). The rest controls whether the loop goes round again, and smuggles the result out of the while. I'll admit that this stuff is probably not of interest to anyone else, but I found the exercise helpful to get a better understanding of tail calls, and to help me port Scheme code into Emacs without worrying about the stack size.

Pattern matching

Lots of cool languages like ML, Haskell and Erlang use pattern matching extensively, and it's a pretty nice way to write some kinds of programs. I've become so addicted to programming with Chicken Scheme's pattern matching unit that I can hardly work without it. So in my wok-match.el library I have made a kind of minimalist implementation of the basic matching features I crave. Here are a couple of examples to give a taste.

Finding the last element of a list:

(defun last (list)
  "Returns the last element of a list."
  (wok-match list
    ((item) item)
    ((head . rest) (last rest))))

(assert (eq 'c (last '(a b c))))

Parsing simple kinds of expressions:

(defun simple-eval (expression)
  "Evaluates trivial infix plus and minus expressions."
  (wok-match expression
    ((a '+  b) (+ (simple-eval a) (simple-eval b)))
    ((a '-  b) (- (simple-eval a) (simple-eval b)))
    (number    (if (numberp number)
                   number
                 (error "Unparsable expression %s" number)))))

(assert (= 3 (simple-eval '(1 + 2))))
(assert (= 6 (simple-eval '(1 + (2 + 3)))))

One of the things that I've found pattern matching especially useful for is decoding messages that come in over the wire in S-expression-based communication protocols. This can be useful for communication between different kinds of Lisp systems (Emacs and Scheme code for example). This leads to Erlang style code where server processes loop, matching and unpacking incoming messages with a very simple and pleasing syntax. Well, I think so anyway.

;;; Bogus code for processing robot control messages.
(wok-let loop ()
  (wok-match (read socket)
    (('lights-on)         (set-output-port *light-port* 1)
                          (loop))
    (('lights-off)        (set-output-port *light-port* 0)
                          (loop))
    (('turn-left degrees) (set-output-port *left-track-port* -1)
                          (set-output-port *right-track-port* 1)
                          (sleep (* (abs degrees) *rotation-time-per-degree*))
                          (set-output-port *left-track-port* 0)
                          (set-output-port *right-track-port* 0)
                          (loop))
    ... various other bogus instruction patterns here...

C++

The rest of the stuff currently in my Wok is Lisp gadgetry for hacking in C++ (like many others before me I'm sure). More on that soon. I have been trying to mimick features I've seen Eclipse do with Java - for now let me just say that C++ syntax is a serious can of worm.

Thanks to Jan van der Crabben (Photographer) for the beautiful wok picture (from Wikipedia).

(*) I guess the real reason is that I first worked on a shared machine that didn't have enough RAM for Emacs - it was some kind of 80486 machine with something like 32MB or 64MB of RAM, shared by several people on Wyse terminals, in the eary 1990s. It was a shoestring operation but we did some cool stuff with it.