This blog post was originally posted on my blogpost blog at this URL, and was later migrated to this place. There may be some comments at the original URL.
In this blog entry, I am only going to describe one of my learning experiences from past few weeks. No programming pearl today.
About a year ago or so, I learnt about this book named Land of Lisp. The website is just amazing, and I was mighty impressed after going through the video and the comics on the front page. I decided to get the book. Here are some reasons for why I made that decision:
- I am a big fan of Head First series from O’Reilly, and this book follows a similar hat ke style.
- I worked through some Clojure books two years ago, and a lot of things in Clojure come from Common Lisp. Learning their historic roots often helps me understand concepts much better.
- I wanted to learn about reader macros, CLOS, and condition system - three things not present in Clojure. (Well, the condition system is available as an external library, but I came to know that only recently.)
- I wanted to learn about continuations. I know that it’s a Scheme thing, but the site makes a mention of it (see the “guilds” at bottom), and so I presumed the book covers that.
I got the book, and started working through it a couple of weeks ago. I decided to port the examples to Clojure as I read.
The first six chapters are introductory level, and quite slow-paced. Head First series books are similarly slow-placed, but at the time when I read them, I was just getting started with the whole programming thing, and so the pace suited me, but it doesn’t any more.
In seventh chapter, we create a small graph-util library that draws graphs from the given information using graphviz. (Sidenote: I have ported it to Clojure, and put it up on github for your reading and forking pleasure.) This chapter is where my gripes about the language start. To represent the nodes, author uses a list of lists. Each nested item represents a node, with the items in cells corresponding the attributes of a node. Edges are represented in a bit more complicated manner - with a list of lists, which again contain some more lists. The problem gets worse during processing where some of the functions use
maplist on edges’ list and therefore have to deal with a nested structure of depth 4. No attributes are named (i.e. records, maps aren’t used), and so processing code is littered with calls to
cadar etc. It took me over a day to understand one of the functions from the undirected graphs section. I don’t know about you, but I really really dislike this kind of code.
In eighth chapter, we create a game named Grand Theft Wumpus. I flipped through the chapter to take a look at the code, and my fear turned out to true. The code employs the same kind of list processing style that caused me headache in the previous chapter. I gave up my resolve of working seriously through the text, and decided to only skim through the rest of the book.
A quick overview of rest of the book: The chapter on generic programming is nice. Clojure also has that feature - under a name multimethods, and it is even more powerful thanks to the ability of dispatching on arbitrary predicates. The author appears to be confused about the distinction between records and objects, and tends to refer to any use of records as OOP style. The coverage of CLOS is not as in-depth as I would like. I skipped the next few chapters on
format facility, streams, condition system etc. I am already well versed with functional style of programming, so I skipped the chapters on that as well. The chapters on macros and DSL were enjoyable, and provided some motivating examples for macros. And as it turns out, the book does not cover continuations, which was a bit of a disappointment.
Overall, it’s a good book if you want to learn the basics of Common Lisp in a fun way. However, if your goals are similar to mine (the ones that I mentioned in the beginning), there is not much you’re going to gain from this book.
Now a few complaints about the language itself (with some side-by-side Clojure comparisons):
- It’s a Lisp-2. The topic of Lisp-2 vs Lisp-1 is perhaps as contentious in the Lisp community as static vs dynamic is in the PLT community. So I’ll say this: I prefer Lisp-1. I understand that both approaches have their merits and drawbacks, and it ultimately comes down to what trade-offs one is ready to settle for. My choices lead me to the path of Lisp-1.
- Non-descriptive, cryptic, and possibly misleading identifier names. Without firing a Google search, can you tell me what
prognstand for? Quick, what do you guess
labelsmacro does? What do you think
assocfunction does? In what ways do you think
eqdiffer from each other? What about
mapl? Then there is this
remove-if-notfunction which is a double negation, and could instead have been named
retain. I could go on and on. I agree that when programming functionally one often needs to invent a new vocabulary, but I am of the opinion that these neologisms must be chosen carefully and not be misleading. Common Lisp falls flat on its face in this regard. 0.Truthiness semantics are simple (
nilis false, everything else is true), and lead to an elegant programming style where one can just return computed values instead of true and false. However it has some peculiar corner cases, which make one question the worthiness of this style. Again, I concede that this is subjective matter, and depending on what trade-offs one finds more acceptable, one can have an opinion different from mine.
- Equality in Common Lisp is a complete mess. There are functions like
=etc. (You can look up the online references if you wish to learn about how they differ from each other.) Clojure gets this right:
=for value equality,
identical?for referential equality. That’s all.
let*macro. Quetzalcoatl knows why the ability of binding sequentially is not present it
letitself. (There is some discussion in this stackoverflow thread.) Clojure gets this right, too.
loopmacro. I think this one is just horrible. Why anyone thought this would be a good idea is beyond me.
- Multiple return values. The semantics are complicated, and not worth the gain. One can just return lists/vectors, which is what Arc and Clojure do.
- As I mentioned before, processing lists with
caddretc is a pain. Clojure has destructuring which makes list processing way more pleasant. Also, when appropriate, Clojure encourages use of maps and records over lists, which adds to code clarity.
- Setting up the environment is hard for neophytes (like me). IDEA (my preferred IDE), Eclipse, and Netbeans do not have plugins for Common Lisp. I set up Emacs and SLIME. That took two days of yak-shaving. For building, I was going to get Quicklisp and asdf, but I gave up in exasperation. This is a not a complaint about the language per se, but its ecosystem, and therefore an important one.
Common Lisp is a Lisp, and one could argue that some of my above complaints could be resolved with a bit of language-taming. But that’s not the point. The above is the criticism of not the language alone, but also its standard library, its widespread conventions, its ecosystem, and to an extent, its community. Judging by all these factors, I am not impressed.
As a side-effect, this book has left me with an added appreciation for Clojure and Rich Hickey. Clojure indeed simplifies a LOT of things from Common Lisp to make developers’ lives easier (oops, I meant simpler ;-), and kudos to Rich Hickey for that.