TheJach.com

Jach's personal blog

(Largely containing a mind-dump to myselves: past, present, and future)
Current favorite quote: "Supposedly smart people are weirdly ignorant of Bayes' Rule." William B Vogt, 2010

Briefly revisiting Land of Lisp

A couple months ago I was surprised to read in a couple places that the book Land of Lisp was "much maligned". This was surprising because it has high reviews on Amazon and GoodReads, and I personally liked it overall too. I remember getting it not long after it came out, Christmas of 2010, but I didn't really make time to read it much. I was busy with college and my job, and at some point I got more hooked on Clojure and didn't want to look at things through the "old and crufty" CL lens. I wish I kept track better of my progress milestones. I remember going through a section of it years later with my cat occasionally resting on a page. For various outside the book reasons though I just didn't get around to making the final push to finish it until 2018.

I had one major critique, which was the author seems to have a stylistic preference that I don't share. Namely, his Lisp code is more "Scheme-like" than Common Lisp-like, and more emphasizes functional programming (mostly in the closures + higher order functions sense, not so much the immutability sense, and ignores the static typing sense). To quote from my own thoughts after reading that I still agree with:

The main critique I'd offer would be that perhaps functional style was emphasized too much, much of the code felt like what I'd expect to see if a Scheme or Clojure programmer was forced to write in Common Lisp. (So it didn't actually feel "sticky" to the brain and writing Lisp afterwards I still look basic things up.) Specifically, the over-frequent use of inner functions via 'labels for closure benefits rather than separate helper functions with a couple extra params (or generic methods on objects...), recursion (and often not tail-recursion) instead of various looping facilities, and raw car/cdr and their (admittedly useful) mutant forms rather than some structs or CLOS objects to help hide that. I just can't remember that the caddr of our "data structure" is always some particular thing, I'd like a get-thing that does the caadr/caddr/whatdr.

I have various other minor criticisms but I can excuse them, they're minor. Anyway, on to other people's criticism, that I want to now waste time criticizing myself... The link I was given two months ago was http://metamodular.com/Books/land-of-lisp.html. It's written by beach (IRC handle), who is a venerable and well-respected Lisp programmer, so it's worth paying attention to what he says. Minor side note: this is also true of Rainer, who sometimes goes by lispm (though HN handle apparently changed to _19qg) and who is prolific on stack overflow.

However for both of those individuals, sometimes their comments come off as just overly pedantic. Technically correct, yes, but pedantic all the same. So while it's always worth listening to them, it's not always worth taking what they say to heart, if that makes sense.

So looking at this review, it starts off with a very strong sentence: "This book should not have been published in its current form."

What is the first error noted down?

Page 33. "Symbols in Common Lisp are case-insensitive". That statement is just wrong.
Yes, it's wrong, but it's "right enough". This isn't an error big enough to merit a judgement towards saying the book ought to have not been published with such errors in it. I would agree that it could have used another round of editing. I could see going either way here on making the statement technically correct -- either explaining the full details right here in the beginning, and noting how you can use |piPes| to specify a mixed-case symbol, or (make-symbol "FoO") which will not even be eql to 'foo. I could also see simplifying it to push that discussion for some other time (if ever) and just rephrasing to something like in most implementations, including the one we're using in the book, symbols without any special treatment are automatically upper-cased and thus can be treated as case-insensitive in most places.

His next complaint is on what character sequences determine a floating point or integer. There's an implicated insertion into the author's wording that isn't there, that the author meant "uniquely determined" when he just wrote "determined", but the 234. case is a fine counter-example even ignoring that. Ok fine. Still it's mostly correct...

It goes on. Ok, next chapter, which covers basic if conditions... (The terminology "conditions" is common to broader programming.) The critiques of this chapter are almost entirely about terminology.

And then he just stops with the critique. That's it. Two chapters, those being attempts at explaining the most basics of Lisp syntax to newcomers. I wouldn't disagree if someone said these two chapters weren't very good introductions to Lisp syntax, there are better out there, but I think they're serviceable enough, and the errors, while nice to have been corrected, don't merit the status of "should not have been published", especially for the entire rest of the book. I don't even really consider the book's beginning to be until chapter 5.

Anyway, the topic of general criticism on the book passed, but I heard more come up again tonight. This time though it was from a different post, here: https://stevengharms.com/posts/2025-01-30-quitting-the-land-of-lisp/

I read it and while I think there are valid complaints, I think there are many misdirected or invalid ones too. I feel bad for the author because I think he got misled somewhere that Land of Lisp is a book aimed at beginners to programming itself, and most of his criticisms would hit harder if that were the case, but it's not.

He writes:
supposedly beginner-friendly book
But it's not that. It'd be wrong to market it as such. I'm left wondering, how did he get that impression?

...Unfortunately there is some marketing to suggest that, and the presence of chapters 1-4 sort of justify it as well, though again I see chapter 5 as the real start of the book.

What is the marketing? It starts with the tagline: "Learn to Program in Lisp, One Game at a Time!"

See, this is basically true, but (my own turn for being pedantic?) I think it'd be better phrased as "Learn to Program with Lisp", or using Lisp, and even better if you say "Lisp features" rather than the generic idea of Lisp itself. The preposition in shares too much similarity with other usages that just don't map well to what this book actually is and does.

If I buy a book that says "Learn to Program in Python", I would kind of expect that after reading said book, I'd be able to go off and write some Python programs of my own, in a sensible way that other Python programmers will understand and accept, maybe even allowing me to contribute on existing projects.

But you don't get an analogous experience for Lisp with this book. If you wanted that, the gold standard is still Practical Common Lisp, albeit you probably should know a language that's not Lisp first, and that book is a little weak on topics like code organization and code quality. (Norvig's PAIP is actually good for code quality. Successful Lisp is another overlooked book I like quite a bit that is both fairly comprehensive, if dated, but also very fast to go through.) No book is perfect. If you don't know any language yet, then (also linked by the blog author) CL: A Gentle Introduction to Symbolic Computation is still the gold standard. (Or so I'm led to believe by people. I knew multiple languages before Lisp so I haven't bothered reading that book myself. I once gifted it to someone who probably won't ever get around to reading it or learning to program (that's ok), and at that time I did skim it a ways and convinced myself that yeah, this seems like a fine way to learn a first language using Lisp if you've got no concrete goal in mind of what you want to program.)

The next bit of marketing is more egregious. The home page links near the top "Slashdot.com book reviews gives it a 10 out of 10 rating!" and the publisher page gives a blurb "An excellent book for someone who wants to learn how to program." This comes from this review. I think it's a misleading line in a misleading review. Bad review, even.

Additionally I found a random reddit thread that claims the author in an interview said he was forced by the publisher to add introductory material, like chapters 2-4. This sort of explains their lower quality I feel but also the source of mismatch in setting expectations. The publisher is probably happy to market it towards the wrong audiences if it gets them more sales...

Ok but, now let's look at what marketing might suggest it's not a book oriented towards beginners?

At the top of the site, there's a free Sample Chapter. This is of course Chapter 8, the Wumpus chapter, which the blog author above spends much time hating on. I think if he had glanced at that sample chapter before starting the book, he might have saved himself some pain, since it seems kind of clear from the sample that this book isn't aimed at beginners.

Next, let's look at perhaps still the best part of the whole thing, which is the beloved music video.



Pay close attention to the speech before the music starts.

A long time ago, a young Lisper was reading an Eighties games magazine... you know, the kind with code for BASIC games printed in it. And he thought to himself: "You know what would be AWESOME? A book with game code in it just like this. But which teaches all the important concepts of Lisp programming! It would use the granddaddy of the Lisp languages: Common Lisp! But would teach all the important ideas for all the Lisp dialects... whether Clojure, Scheme, Arc, or Emacs Lisp.

That is what this book is. It's reminiscent of magazines that described a game and included some code you could type into your computer character by character, without needing to understand anything, and bam, now you can play the game. For Land of Lisp, you can do this as well, it's setup to let you type everything into the REPL line by line, function by function. You really don't want to do this, please use an editor with slime integration, but you can. Then, besides just distributing code for quirky small games, the author also wants to explain how the code works, too. Then, beside just writing game code in whatever preferred style and with professional standards, the author also wants to elucidate all of what he sees as the important Lisp features, no matter which dialect they're most emphasized in. He uses Common Lisp, because it's the only one capable of expressing all of the others' preferred styles (and because it's the best -- or maybe that's my opinion =P).

This leads to something that is very much not beginner friendly, and not even all that practical, just quirky and fun if you like quirky. In order for learning to stick, you need consistency, and this book is about showing a bunch of different things, not a consistent whole, so that's why I think a lot of things in it won't stick at all even if they're understood. That's fine, just knowing the things exist lets you look them up later. It's important and useful just to know about things. Like, if you've never encountered the concept of lazy evaluation before, are you going to think of it when it actually may be just the thing you want for a particular problem? You can look up how to achieve it in your working language at that point. (Python generators and coroutines were probably my first exposure to equivalent things. You can make a single-threaded but concurrent multi-tasking operating system kernel with such things.)

So with this book you're going to learn a bunch of features not by systematic or consistent order or even super motivating examples, but also by means of writing little games -- small quirky games, not AAA attention sinks with tightened up graphics. He's going to write this code in a functional programming style (in a Scheme sense, not so much a Clojure sense), he's going to write this other code with a lazy evaluation style (in a Clojure sense), he's going to use a lot of dynamic scope here (in an emacs lisp sense), he's going to macro up a bunch of stuff with some terse shorthand there (in an Arc sense), and he even shows a little bit of object oriented programming from CLOS too. Or at least, that's the vision. For the last three, it didn't really come to be, and just sort of mentioned off-hand in the epilogue. He does show some things with defstruct, but not defclass; I think perhaps he doesn't like OOP very much.

bulky state dragged around by objects

(And again some of the marketing blurbs say "With Land of Lisp, the power of functional programming is yours to wield." but Common Lisp isn't particularly functional like Scheme or Clojure are, even if the author's style leans heavily that way.)

Ok, so back to the author's blog post critique. What other things stood out to me?

Yep, it inverts the patterns. Surely there’s a good reason? If so, the book doesn’t provide it.
I'm pretty sure the book states that it does this to demonstrate the concept of shadowing with assoc -- when you "pickup" an item, you don't have to modify any locations' objects, you just push the item and its new location ('body) to the object locations alist. This also gives you a "history" of inventory pickup for free. Someone might wonder "but what about 'using' or 'dropping' an item?" Same thing: just push it to a new "location" called 'used or 'dropped (or probably better, the 'location it was dropped), it shadows its presence in the 'body/inventory.

Chapter 6 sees us building a custom Read-Evaluate-Print-Loop
This was also done for the Ch 5 dungeon traversal, game-repl...

Let’s suppose that you’re on an old computer or it’s a computer that you’re uncomfortable installing software on.
I find this a bit of a stretch; you already installed a Lisp implementation. Graphviz installation is easier. In my case I already had it installed because I've long been a user and fan of graphviz anyway. Besides, I like it when books teach me about the existence of new and useful tools. I don't get the frequent complaining about graphviz.

Land of Lisp writes as if you’re typing in hundreds of lines of code to run the program that Barski is demonstrating.
In one sense, yes, he expects you to type the code in and run it and that's the best way to work through the book. But you don't need to type it in all at once unless you're just anxious to get straight to playing the game. You type in chunks.

He has some larger functions in places, but I believe every function is written before usage, so you can type everything in line by line and function by function. You can usually pause after each one and check your understanding, immediately test it out with some dummy data, and then move on. Everything is also documented in prose, even if the prose lacks in many places. (I don't think I could tell you how alpha-beta pruning works anymore and wouldn't go back to this book for a refresher.) How I went through the book was I had everything in a single landoflisp.lisp file and I typed almost everything in my editor and then sent each top level form to the REPL after typing it one by one. Sometimes I'd then test it out, or sometimes I'd test inner parts out specifically that I didn't fully grasp or wasn't convinced by.

For example, I found part of the definition of walk to be strange, so immediately after I convinced myself the find function was fine.

(defun walk (direction)

(let ((next (find direction
(cdr (assoc *location* *edges*))
:key #'cadr)))
(if next
(progn (setf *location* (car next))
(look))
'(you cannot go that way.))))

(find 'y '((5 x) (3 y) (7 z)) :key #'cadr)


At least, that's how I interpret this stray find line that's left over in my source file.

And to end this section, totally a fair point, the book should have spent some time on setting up an editor with slime. It's nice that you can just type everything directly into a bare repl with no syntax highlighting or paren matching support, but you really shouldn't unless you really are craving that 80s nostalgia. (I was born in '90.)

Chapter 8 [criticisms]
I mean before I bought the book, I looked at the sample chapter, which is chapter 8. I think if the author did the same they would have concluded early on that the book wasn't for them and saved themselves some pain.

It's also the chapter that really justifies the graphviz usage. Graphviz is cool, people should use it more! But in this case it's cool that we can use it to generate a picture of our game world, and update it as we go along playing the game.

Lisp and Lisp instruction has existed since the early 60’s without graph drawing toolkits being readily available. Why this tool? Why now?

I mean yes, sort of, though if you move to the 70s and 80s Lisps had good GUI support, usually included, we're in a sad age now when GUIs with Common Lisp are rather unreasonably difficult. In any case, this criticism is all downstream of the misconception that this is a beginner-oriented book for teaching Lisp, and not what it actually is (a book about making quirky games while demonstrating the various Lisp features even if they're not the most justified for a usage). The quirky games part is important: it's not really a book about game design, true, but it is a book about making games. Graphics beyond 60s terminal output are appropriate.

wumpus map

Just look at that.

wumpus map2

Or that.

Now you could go old-school map-less dungeon crawler aesthetic and demand the player bring their own pencil and paper to draw out their own map as they play along. But we're making games without such a constraint, it's appropriate to have graphical output. And using graphviz in this way is pretty cool. Do you want the author to implement their own graphviz and png outputter in Lisp? Later chapters end up with a website and drawing svg images. A normal game programming book would move on to bringing in a full GUI library sooner or later. (A book I remember fondly is the first PyGame book, which starts with the probably publisher-obligatory intro to Python syntax and then a terminal tank game (that I re-implemented in C once to convince myself about kinda doing OOP in C) but then quickly brings in graphics with pygame.)

But you can test this hypothesis a few paragraphs later
No, you should be testing it in your REPL, having typed along, and better yet an editor with slime.

I think this book does expect you to type along, to have the code loaded and ready to run for your own little experiments and learning. It's helpful. You will struggle if you try to just passively read, and that's probably all the more reason to hate the first few publisher-mandatory beginner chapters because experienced programmers (like the author) will tend to just skim and think they can do it with the whole book.

Now at the risk of the author accusing me of lacking empathy, I really must say, I struggle to understand how an experienced programmer learning Lisp could struggle with the screenshotted code for several days. And I don't think it's because the author isn't 22 and doesn't have an entire day to grok it. It's just not that complicated? It's 30 lines of code, all self-contained to solve one problem. It's got 8 paragraphs immediately following of more explanatory prose.

Now sure, it's not great prose. Perhaps the difficulty was with the get-connected function? The prose states "The usual way to find connected nodes is to start a visited list, and then perform a search along connected nodes, starting with the source node. Newly found nodes are added to the visited list with the push command. We also traverse all the children of this found node, using mapc."

Yup, this is just a normal "usual" depth first search but instead of stopping the search at some target node we just keep going until we've exhausted the search space and return that. (Perhaps an "unusual" method would be to use union-find, which is nice in general for connected components types of problems, but that's more complicated than this use-case demands.)

I had an advantage in understanding this function because I had written code doing DFS in various forms many times before this book, and possibly even in Lisp... If this is your first time encountering DFS, this is not a good explanation, and this is also not my preferred style of doing it. i.e. I prefer a loop over recursion. My preferred approach would look something like this:

; instead of given a node and edge list to get the direct edges,

; given a container (of all nodes and edges data -- could be a hash table,
; or an array, a custom object, an adjacency matrix/list, or,
; like the book, an edge-list)
; and given a specific node (or often, a node location)
; return the neighboring nodes (or node locations) directly
(defun get-neighbors (container node)
(mapcar #'cdr (direct-edges node container)))
(get-neighbors '((a . b) (b . c) (a . d) (e . f))
'a)
; '(b d)

(defun get-connected-nodes (container node)
(let ((result (list node))
(seen (list node))
(frontier (get-neighbors container node)))
(loop while (plusp (length frontier))
do
(let ((current-node (pop frontier)))
(unless (member current-node seen)
(push current-node seen)
(push current-node result)
(dolist (neighbor (get-neighbors container current-node))
(push neighbor frontier)))))
result))
(get-connected-nodes '((a . b) (b . c) (a . d) (e . f))
'a)
; '(d c b a)

; Notes on get-connected-nodes:
; I seem to use "seen" or "visited" as names at roughly the same frequency.
; Some literature uses "expanded".
;
; 'seen would be better as a set or hash table.
;
; 'result ends up being the same list as 'seen, so you could drop it here,
; but this gives flexibility in general for making a better data structure
; for the seen information separate from the type of collection you expect
; to return for the collection of connected nodes.
;
; 'frontier is also a list here, but could better be another collection type as well --
; a priority queue gets you 90% to Dijkstra's algorithm.
; If testing for frontier membership is cheap, it would be more correct to do so before
; adding a neighbor to it, but in practice it doesn't matter much since a node that gets
; added to the frontier again will be popped, noted as having been seen, and we move on.


A terse prose explanation for get-connected-nodes would be something like "Starting at node, perform a DFS but only stop once all reachable (connected) nodes have been visited, returning that collection. A DFS works like this: from a starting node, add it to a seen list, and create a frontier list of neighboring nodes from it. While there are still nodes on the frontier list, pop one off. If we were searching for a specific node, we could check if we found it, and return that we did, or continue on searching if we didn't. Since we don't have a specific node in this case, we just continue on searching. To do that, check if the node has been seen on the seen list before. If it hasn't, add it to the seen list, then get its neighbors, and add those to the frontier list."

Anyway... maybe the difficulty wasn't with the algorithm, but with the syntax, like the use of recursion and labels? I'm not sure if the beginning chapters would have been enough. They definitely wouldn't have though if someone just skimmed the chapters rather than typing in and running each code block. I was an experienced programmer already when I read this, but I still typed code in from the beginning. I don't know if the following is from the book or my own "convince myself, just in case, of the difference between flet and labels" experiment, but it comes right after the number guessing game code, might date to early 2011:

(flet ((f (n)

(+ n 10)))
(f 5))

(labels ((a (n)
(+ n 5))
(b (n)
(+ (a n) 6)))
(b 10))


I would love to see a live stream of how the author goes about reading this kind of book. For my own code that survives for this section, I notice that after many of the functions I typed in, I also typed in a sample invocation and commented its expected output. I didn't do it for everything in the book, but the practice is normal and I think is probably a fair criticism of the book to not emphasize it more. This interactivity of being able to define and then call is a superpower, and captures most of the value of test-early philosophies too. If you want, you could even embed it into a test docstring like was fashionable in Python back in the day. But here's what I mean if it's not clear:

;; given an edge-list, gets the edges that node connects to.

(defun direct-edges (node edge-list)
...)
(direct-edges 'a '((a . b) (b . c) (a . d) (e . f)))
; '((a . b) (a . d))

(defun get-connected (node edge-list)
...)
(get-connected 'a '((a . b) (b . c) (a . d) (e . f)))
; (d c b a) ; c is connected through b

(defun find-islands (nodes edge-list)
...)
(find-islands '(a b c d e f) '((a . b) (b . c) (a . d) (e . f)))
; '((f e) (f) (d c b a))


I also noted in the function after some distaste with the edge-list concept, saying:

; more natural adjacency list structure:

; '((b (c)) (a (b) (d)) (e (f)))


Maybe the difficulty wasn't so much the code, just the fact that you can't expect to learn anything in chunks that are the size of a baby's nap? I've never had a baby (though of course I have friends that have, and I still would expect them to not spend days on this), how long is a baby nap anyway? Google suggests anywhere from 20 minutes to 3 hours and depends on the age of the baby. I don't know man, if I could only take a crack at a programming book that's also best done by typing in the code, and could only work in 20 minute chunks where then I'm interrupted for hours between each chunk, I think I would just put the book aside for a year or two until my baby is no longer a baby and I have more contiguous time. I wouldn't fault anything with the book because of my particular circumstances making it impossible to learn much of anything from any book.

Final thoughts... I do agree that the book could have used a better editing pass, including a technical review pass to catch the more trivial things beach pointed out. I do agree that the topic of debugging and interactivity needed way more treatment than it got. (At least now there's a whole book on the condition system if you want it.) There are other super powers that were glossed over (like defclass anything, CLOS as a whole, dynamic variables vs. lexical variables) or totally ignored (like deftype, type, concurrency, and optimization declarations with assembly checks and maybe even going as far as generating your own assembly -- though that super power kinda requires SBCL to see).

Like, to briefly digress into my own Lisp history, it wasn't until 2016 that I actually became seriously interested in Common Lisp, and from that I lost all my previous infatuation with the ideas of "elegance" in Scheme and Clojure. What triggered that was an innocent HN comment showcasing SBCL's compile-time (!) warnings about variable typos, improper types, dead code elimination... and not long after, seeing how SBCL would use such type information to generate more optimal assembly code, which you can check any time with disassemble. By this time I had read a good chunk of Land of Lisp, and other Lisp books or resources, and some Scheme and Clojure ones too, but at that point I thought "Wow, I thought I knew Lisp, but I did not know Lisp." Once I started to actually know Common Lisp, I loved it and don't like using anything else, including the other so-called "dialects".

It's possible to forgive some of the book a little bit by noting the publishing date context. 2010. A book on programming in a language would be remiss to not mention its library distribution solution, but QuickLisp only came out sometime in 2010. Java 1.7 hadn't even come out yet. Closures were still kinda new and scary to a lot of programmers. (Proper and tasteful usage of them still is.) Perhaps some of the awesome SBCL features weren't there yet.

Today, in 2025, I wouldn't recommend it as a should-read book, let alone a must-read. It's also not particularly good as a reference. It's not particularly good for Lisp evangelism either (the music video excepted). Still, I liked it, I still like it after thinking about these two people's serious criticisms. It was worth my time. But I'd only recommend it to someone who thinks they'd enjoy the style and quirkiness of it all, and things like seeing a bunch of disparate features (but not all CL has to offer) in this simple games context. And even then, it shouldn't be their first Lisp book. They should also know enough to not use CLISP and to use a proper editor. (I use vim.)

I don't begrudge the blog author for not continuing the book and he's probably right to never come back to it. I hope he gives Lisp itself more chances though. He can take his time and get to it whenever, it's not going anywhere.


Posted on 2025-07-07 by Jach

Tags: lisp, programming

Permalink: https://www.thejach.com/view/id/444

Trackback URL: https://www.thejach.com/view/2025/7/briefly_revisiting_land_of_lisp

Back to the top

Back to the first comment

Comment using the form below

(Only if you want to be notified of further responses, never displayed.)

Your Comment:

LaTeX allowed in comments, use $$\$\$...\$\$$$ to wrap inline and $$[math]...[/math]$$ to wrap blocks.