11 December 2009 2:23 PM (scheme | guile | meta-circular | evaluator | compiler)
There was some interesting feedback on my last article, and I ended up wanting to say too much in reply that here I am again, typing at the ether.
Stephen reflects the common confusion that somehow this is just a little file that might or might not work. No man, this is Guile! That's the implementation of Guile's eval. So unit tests, yes, we have more than 12000 of them. Only some of them specifically test the evaluator, but all of them go through the evaluator.
But to be honest, I think unit tests help, but when I'm hacking the compiler the most useful test is to simply rebuild the compiler. If it successfully bootstraps, usually we're doing pretty well.
Zed raises a more direct criticism:
This is why I hate lisp: [link to my article] Long and dense, no comments, not semantic. That's "awesome code"?
I don't think I used the word "awesome", but yes, I believe so, in the sense of "inspiring awe" -- at least for me.
I'm going to call on my homie Alan Kay for some support here. There was an oft-cited interview with Alan Kay a few years back, when he described eval as "Maxwell's equations of software". I quote:
[Interviewer:] If nothing else, Lisp was carefully defined in terms of Lisp.
[Alan Kay:] Yes, that was the big revelation to me when I was in graduate school—when I finally understood that the half page of code on the bottom of page 13 of the Lisp 1.5 manual was Lisp in itself. These were “Maxwell’s Equations of Software!” This is the whole world of programming in a few lines that I can put my hand over.
I realized that anytime I want to know what I’m doing, I can just write down the kernel of this thing in a half page and it’s not going to lose any power. In fact, it’s going to gain power by being able to reenter itself much more readily than most systems done the other way can possibly do.
Just for context, here is that half-a-page -- Maxwell's equations of software:
evalquote is defined by using two main functions, called eval and apply. apply handles a function and its arguments, while eval handles forms. Each of these functions also has another argument that is used as an association list for storing the values of bound variables and function names.evalquote[fn;x] = apply[fn;x;NIL]
whereapply[fn;x;a] = [atom[fn] → [eq[fn;CAR] → caar[x]; eq[fn;CDR] → cdar[x]; eq[fn;CONS] → cons[car[x];cadr[x]]; eq[fn;ATOM] → atom[car[x]]; eq[fn;EQ] → eq[car[x];cadr[x]]; T → apply[eval[fn;a];x;a]]; eq[car[fn];LAMBDA] → eval[caddr[fn];pairlis[cadr[fn];x;a]]; eq[car[fn];LABEL] → apply[caddr[fn];x;cons[cons[cadr[fn]; caddr[fn]];a]]] eval[e;a] = [atom[e] → cdr[assoc[e;a]]; atom[car[e]] → [eq[car[e];QUOTE] → cadr[e]; eq[car[e];COND] → evcon[cdr[e];a]; T → apply[car[e];evlis[cdr[e];a];a]]; T → apply[car[e];evlis[cdr[e];a];a]]
pairlis and assoc have been previously defined.evcon[c;a] = [eval[caar[c];a] → eval[cadar[c];a]; T → evcon[cdr[c];a]]
andevlis[m;a] = [null[m] → NIL; T → cons[eval[car[m];a];evlis[cdr[m];a]]]
From the LISP 1.5 Programmer's Manual
What a mess! Where are the unit tests? Where are the comments? A little bit of whitespace, please! They use "cdar", "caar", and "cadr". They should be using named accessors! What if you eval an atom that's not found in the association list? Did they really name a function "evlis"? Et cetera.
Now, I do think the LISP 1.5 (it wasn't yet spelled "Lisp" then) evaluator is awesome, as it was in McCarthy's first papers on the subject. If you disagree with that, that's cool, I see why one would "hate" my poor imitation.
But given the Lisp history of meta-circular evaluators, one doesn't need to comment every one as if it were the first. In the same way that one recognizes an if statement without the need for a design pattern behind it, I would want anyone who's hacking on Guile's evaluator to have read or perhaps even written several evaluators; and once you've written one, you know the pattern.
More substantively, Charles speculates on source-to-source transformations to ease interpretation. I admit almost total ignorance regarding PreScheme; I've been meaning to learn about it for years now. But the point of this evaluator was to have it use the same representation as VM-compiled procedures; ideally it should run fast, but the first priority is for it to run on the VM itself instead of on a separate stack. There's certainly many more clever things that can be done there, and thankfully since eval is implemented in Scheme and compiled like anything else, it will also reap advantages of an improving compiler.
Regarding Emacs, I hope to say more on that point within a week or so, when the video for my talk at the recent GNU Hackers Meeting gets put up.
Finally, Bubo asks if this work is in a released tarball. Not yet is the answer, but it will be in Tuesday's 1.9.6 release.