Let Over Lambda—50 Years of Lisp

by Doug Hoyte

About the Book
Table of Contents
Text Mode
Buy It
Production Code
Original Code
Support independent publishing: buy this book on Lulu.


The original code, exactly as it appears in the book is here. But in most cases you will want the production code which is a slimmed down version with most of the interesting functions/macros as well as bug-fixes and small additional features. If you are browsing the text online, you will see the original content, exactly as printed.

  • Henry Lenzi pointed out that in chapter 2, a top-level setq on a non-special variable is accidentally used and I rely on CMUCL's (in my opinion correct) handling of this unspecified action.
  • Zach Beane pointed out that (function '(lambda (x) (+ 1 x))) is not ANSI Common Lisp as may have been implied in chapter 2. Do not use this in portable code. The quote character was a typo and should be removed.
  • Daniel Dickison mentioned that dynamic variable implementation requires special considerations in a multi-threaded Common Lisp implementation and this is not described in Let Over Lambda.
  • Henry Lenzi wrote that in chapter 2 "the numerous advantages of lexical scoping are only slowly being taken up by many Blubs" should be "the numerous advantages of lexical closures are only slowly being taken up by many Blubs".
  • The #" read macro from chapter 4 now allows nesting. This was suggested earlier by Daniel Herring but only recently have I found a use for this. Added to the production code.
  • Martin Dirichs sent in a better version of #>-reader. It fixes some corner cases: The string being read can be terminated with any substring of the delimiter string and an empty (0 length) delimiter string is now supported. Martin's version is included in the production code. Big thanks to Martin.
  • Pandoriclet-set from chapter 6 has a minor bug. There is an extra argument to the error form that could cause incorrect error messages. Fixed in the production code.
  • In the Implementations appendix, I say that SBCL doesn't have an interpreter but this is (now) incorrect. It just isn't enabled by default.
  • Giovanni Gigante noticed in unit-of-distance from chapter 5 that nm (nanometers) should actually be um (micrometers).
  • In Chapter 4 where it says "Guy Steele, the father of Common Lisp" I, of course, mean one of the fathers of Common Lisp.
  • In Chapter 3 I was overly dismissive of the Lisp-1 vs Lisp-2 debate. I should have been more clear that while I favour Lisp-2, the debate has not been and probably never will be settled definitively. I don't consider languages like Scheme that use Lisp-1 to be any less lispy than Common Lisp.
  • Drew Crampsie noted that several of the symbol checking predicates like g!-symbol-p will not function correctly if the readtable-case of the readtable in use is not :upcase as it is by default in Common Lisp.
  • Tatsuya BIZENN noticed in Chapter 8 at the bottom of page 305 where it says "the base variable" it should be "the state variable".
  • Ian Mondragon noticed a typo on page 196: "Second, a single syntax for both accessing and setting an accessor *is is* implemented with get-pandoric and defsetf."
  • The block-scanner function from chapter 2 is buggy. This was first noticed by Breanndán Ó Nualláin. Thanks also to Herman Vreuls and Ciprian Dorin for sending in correct versions that work by back-tracking. However, here is a non-back-tracking version that implements a very simple Thompson NFA:
    (defun block-scanner (trigger-string)
      (let ((trig (coerce trigger-string 'list))
        (lambda (data-string)
          (loop for c across data-string do
            (let (new-states)             
              (if (char= c (car trig))
                (push (cdr trig) new-states))
              (loop for s in curr-states do
                (if (char= c (car s))
                  (push (cdr s) new-states)))
              (setq curr-states new-states))
            (if (remove-if-not 'null curr-states)
              (return t))))))
    Note: Usually it is not a good idea to write parsers like this unassisted. Instead, there are excellent tools like Ragel to help implement such non-back-tracking "block scanners" (though note Ragel "scanners" are different than block-scanner — they do back-tracking). The only thing Ragel is missing is a Common Lisp output target.
  • There is a bug in the #> reader in chapter 4.3 similar to the bug in block-scanner described above. For a more complete, flexible, and bug-free implementation I suggest Alexander Kahl's cl-heredoc package (github).
  • Alexey Romanov noticed that in chapter 3 the sleep_units example C function's switch statement argument should be unit, not value.
  • Kartik Agaram has been doing some interesting work in extending defmacro! and family macro utilities.
  • Bernard Hurley found a bug in pandoric-eval. It accidentally captures the variable sym due to sloppy coding on my part. This will be fixed in the production code very soon.
  • Jon Snader noted that in chapter 7 where it shows the superior disassembly it should call sn-to-lambda-form not the inferior sn-to-lambda-form% version.
  • Robert Spector noticed that at the beginning of 2.3 it says "that variable is said to be" where it should be "that fragment of code is said to be". Robert also reported several embarrassing typos throughout the book.
  • David Bakin noticed that Algol-60 was incorrectly described as having indefinite extent. This feature was introduced in Algol-68, not Algol-60.

Thank you to everyone who has discovered errata. Please send any additional errata/comments to me.

You might also be interested in the clarifications page where I try to elaborate on Let Over Lambda's message.


All material is (C) Doug Hoyte unless otherwise noted or implied. All rights reserved.