In light of this, I've put together a short list of lisp programming techniques that increase the clarity, robustness, and elegance of your code. Some have been floating around for years, but I don't think they've ever been collected in one place. So have fun...
ELEMENT-GENERATOR. On every call, it side effects a quoted list which is part of its definition. Thus, each time we call it, it returns the next element in its state list.
> (defun element-generator () (let ((state '(() . (list of elements to be generated)))) ;() sentinel. (let ((ans (cadr state))) ;pick off the first element (rplacd state (cddr state)) ;smash the cons ans))) ELEMENT-GENERATOR > (element-generator) LIST > (element-generator) OF > (element-generator) ELEMENTS > (element-generator) TO > (element-generator) BE > (element-generator) GENERATEDIt is a simple matter to generalise this function to take an argument, which, if non-nil, is the new list to smash into
STATE's cdr. Thus we can determine at runtime which list
ELEMENT-GENERATORwill generate its elements from.
(GEN-COUNTER 5), it replaces itself with
(GEN-COUNTER 6)in the body of the program, and returns 5. This is the sort of thing the functional programming advocates are referring to when they talk about making the state of a system explicit in the arguments. Note that this macro rewrites itself, which is what Steele is referring to in the Lambda papers when he describes macros as ''syntactic rewrite rules.''
> (defun gen-counter macro (x) ;X is the entire form (GEN-COUNTER n) (let ((ans (cadr x))) ;pick the ans out of (gen-counter ans) (rplaca (cdr x) ;increment the (gen-counter ans) form (+ 1 ans)) ans)) ;return the answer > ;this prints out the numbers from 0 to 9. (do ((n 0 (gen-counter 1))) ((= n 10) t) (princ n)) 0.1.2.3.220.127.116.11.8.9.T >
GOTOin favor of more restricted constructs such as
IF. We can take this even one step farther -- producing comparable gains in clarity -- if we take advantage of our ability to pass circular list structure to the evaluator. An unconditional loop implemented by a
PROGNwith a circular tail is surely more copacetic and pleasing to the eye than a Lisp
DO, with all its complex syntax. We can further replace conditional loops with
COND's whose clause bodies are actually circular pointers back to the top of the loop. Note, of course, that this is a much more powerful technique than simple
WHILE-DO, enabling us to generate clean solutions to Knuth's examples of loops hard to implement with previous structured programming imperative constructs. Looping with circular progs:
> (defmacro circularize (&rest forms) ;replace the forms with (cons 'progn (nconc forms forms))) ;a circular list of the forms. CIRCULARIZE > (let ((a 0)) ;Simple increment-and-print loop (circularize (princ a) ;Indentation is important (setq a (+ a 1)))) ;for clear code! 0.1.2.3.18.104.22.168.22.214.171.124.126.96.36.199.188.8.131.52.184.108.40.206.220.127.116.11. 18.104.22.168.22.214.171.124.126.96.36.199.188.8.131.52.184.108.40.206.220.127.116.11.52 .18.104.22.168.22.214.171.124.126.96.36.199.188.8.131.52.184.108.40.206.220.127.116.11.7 18.104.22.168.22.214.171.124.126.96.36.199.188.8.131.52.184.108.40.206.220.127.116.11.10If we implement all our branching and looping this way, we can remove the very notion of looping from
, who never has to worry about anything but following ``sequential'' list structure. This simplification of
naturally brings consequent efficiency and performance benefits.
Some short-sighted individuals will point out that these programming techniques, while certainly laudable for their increased clarity and efficiency, would fail on compiled code. Sadly, this is true. At least two of the above techniques will send most compilers into an infinite loop. But it is already known that most lisp compilers do not implement full lisp semantics -- dynamic scoping, for instance. This is but another case of the compiler failing to preserve semantic correctness. It remains the task of the *compiler implementor* to adjust his system to correctly implement the source language, rather than the user to resort to ugly, dangerous, non-portable, non-robust ``hacks'' in order to program around a buggy compiler.
I hope this provides some insight into the nature of clean, elegant Lisp programming techniques.