Thomas Munro <munro@ip9.org>
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.
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
|
After macroexpansion
|
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
|
After macroexpansion
|
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.
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...
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.