First, thanks to all of you who advised me on what Lisp to learn. I found it really helpful to get so many different perspectives, and I hope those of you who advocated Common Lisp or Clojure will not be offended that in the end I chose Scheme. (Actually, I bet you won’t be: one of the pleasant aspects of the comments on that article was the consistent sense that all the comments were of the form “I prefer this Lisp, but if you learn any of them that’ll be great.”)
A few people suggested that I should learn all the different Lisps rather than just picking one. As a looong-term project, I guess I agree with that; but here and now, I need to pick one and run with it: and Scheme it is, for its minimality, elegance and purity. I figured that if I learn Scheme it’ll be easier to move from that to Common Lisp later rather than vice versa. (I was tempted by Clojure, too, not least because there is only one of it, but it feels just a bit too new-fangled to be a good starting point.)
The nice people at MIT Press sent me a review copy, which I am pleased about because I learn better from books than from websites; but for those of you who want to play along at home but don’t want to buy the book, its content is very helpfully online, free, at http://www.scheme.com/tspl4/.
About the book
I chose this book from among several that I was offered, in part because it seems to be modelled on Kernighan and Ritchie’s classic The C Programing Language, which I am a big fan of. The title is an obvious nod, but several Amazon reviewers also made the point that it takes much the same approach, as does one of the jacket quotes:
“Kent Dybvig’s The Scheme Programming Language is to Scheme what Kernighan and Ritchie’s The C Programming Language is to C. Dybvig’s book is either for the novice or serious Scheme programmer.
– Daniel P. Friedman, Department of Computer Science, Indiana University.
I’d have to guess that Daniel P. Friedman is one of those serious Scheme programmers, though, because speaking as a novice, I am not finding it particularly easy going. In part, that’s no bad thing: it’s slow because it’s dense. Although I am only 54 pages in, that’s because those pages are worth several hundred pages of a lesser book — there’s no fluff, a lot of new material on each page, and plenty of exercises.
On the other hand, there are a few places where it seems the author is taking too much for granted, probably because his own familiarity with Scheme culture makes it hard for him to recognise which concepts the rest of us are not already familiar with. For example, the book talks a lot of about Scheme’s “syntactic forms” without saying what a syntactic form actually is. The answer only gradually emerges from among the various examples, and it turns out that syntactic forms have nothing to do with syntax or with form. I really could have done with being told that up front.
[For those who don’t know: there’s only one form of syntax in Scheme, the S-expression. “Syntactic forms” turn out to be evaluation regimes for S-expressions. For example, whereas in the most typical form, “procedure application”, expressions like (cons a b) evaluate every member of the top-level list before applying the procedure, the expression (quote (a b c)) does not evaluate its argument because it uses the “quote” syntactic form; similarly, (or a b c d) uses another form such that it evaluates a, but then continues to evaluate b and the remaining arguments, in sequence, only if all preceding arguments were false — which is why (or #t (/ 1 0)) does not raise an exception.]
How to read the book?
I am actually not clear in my own mind how to read this book. I’ve said before that I like books precisely because they get me away from the keyboard so that I focus on what I am learning rather than, well, all the other good stuff that distracts me from learning when I have access to my email and Hacker News. But it feels like I’m going to learn more from this book, and hammer home the unfamiliar ideas more emphatically, if I do all the exercises as I go through. And because they are so liberally sprinkled, that means going back to the keyboard every page or two.
With the Rails book, which I’m reading in parallel with this, I’ve been reading a chapter at a time away from the keyboard, then going back over the same material with my computer, making all the application changes that the book describes. That’s worked pretty well with something like Rails, where what I am learning is techniques and conventions; I am not sure it’ll work for Scheme, where I am learning concepts. I think I might need that to be more interactive.
So at the moment I am thinking that maybe the thing to do is put the computer aside completely and just read; then, when I’ve finished reading the whole book, go back over the whole thing with the computer, doing the exercises. That sounds long-winded, sure; but then what I am trying to do is no small thing — not just to learn enough fragments of Scheme that I can customise GNU Robots, but to fully learn, to master, the language — to become comfortable with the concepts rather than merely tolerating them. And maybe it’s unrealistic to think that I ought to be able to achieve that high goal with a single pass through a 491-page book, however dense.
But in fact, I’ve come up with another way of reading the book — a yet more inefficient approach that will require me to make three passes! And yet which will, if it works out right, perhaps get me to my goal more quickly. (Hmm. Sounds like I may be over-Fowlering the simple process of reading a book?) Anyway, read on to find out about the three-pass algorithm.
About the language
So far I have mixed feelings about Scheme (and about Lisp in general, bearing in mind that I don’t have more than a cursory Lisp background, and that I am learning Scheme as a Lisp.) The economy and elegance are already obvious even this early in the process — for example, there is certainly something clean about just using (define name (lambda (args) body))) rather than having a separate defun — but even discounting such trivial irritants as the fail function being called assertion-violation, there are … issues.
I suppose I must be frank: I hate all the parentheses and the prefix operators. Yes, yes, I know those are Clueless Newb complaints. I know all about how Lisp syntax is powerful because it’s general and how prefix notation is just as expressive as infix and all that stuff — I’ve read my Paul Graham articles. I hesitated even to mention it here, but, look, dammit, no, it won’t do.
Let’s read some COBOL together!
MULTIPLY UNIT-PRICE BY QUANTITY GIVING TOTAL-PRICE
That is bad syntax. It’s verbose and lumpen. We all know that and (I assume) agree on it, and no-one defends COBOL’s arithmetic and assignment syntax as acceptable any more. We all agree that it’s objectively better to say:
totalPrice = unitPrice * quantity
And yet somehow Lisp’s mystique is so great that we all feel obliged to behave as though we believed that it’s equally good to say:
(define totalPrice (* unitPrice quantity))
But it isn’t. It just isn’t. Syntax matters.
And that goes double for non-trivial arithmetic. The very first exercise in the book is:
Sorry, but that is just dumb. Arithmetic isn’t something you should have to devote precious brain-cycles to, it should just be there. The solution to part a is of course:
(+ (* 1.2 (- 2 1/3)) -8.7)
No-one better tell me that that’s not objectively inferior to the infix version. (I bet someone will, though). And it gets way, way worse once you start writing actual programs as opposed to mere arithmetic expressions.
I will return to the subject of Lisp/Scheme syntax shortly, but first I want to talk about the problem of …
Learning two things at once
I think I could cope with Scheme syntax. I don’t say that I could ever learn to love it, but I think I could cope. But of course that is the very small tip of the iceberg of learning this language. In fact it’s basically trivial — the real issue is learning to think functionally. That is a much bigger and more important matter; and more generally applicable, too. When Eric S. Raymond wrote:
LISP is worth learning for a different reason — the profound enlightenment experience you will have when you finally get it. That experience will make you a better programmer for the rest of your days, even if you never actually use LISP itself a lot.
He was not talking about learning to love parentheses, but about learning functional programming — learning to think about functions as entities in their own right rather than little gobbets of functionality clinging for dear life to the sides of classes. The point of Lisp is closures, lambdas, continuations, list comprehensions, monads and all the rest of the bestiary of terrifying yet alluring functional concepts. You know — all those concepts that your functional-programmer friends like to drop casually into the conversation to subtly remind you that you’re not as awesome as they are. Closures I am happy with, thanks to Ruby; lambdas I understand with my mind but do not love with my heart; continuations I only understand as being “sort of like jmpbufs”; list comprehensions I can’t see the point of; and monads I … uh, lemme see now, I … HEY! WHAT’S THAT OVER THERE!!! [Points into the distance, turns and sprints for safety while you're looking the other way.]
This is big and important stuff. Thanks to my closures-in-Ruby epiphany, I now understand that your functional-programmer friends really are more awesome than me in the sense that they have more tools in their kit. In a frighteningly short time, I have flipped across into that segment of the population that wonders how you can ever get anything done in a language without closures (and, yes, Java, I am looking at you). What if monads turn out to be similarly useful? I don’t want to miss out on them.
But the problem is that I am trying to learn to love Scheme’s dumb syntax and its powerful abstractions both at the same time. And as any good scientist will tell you, you can only really understand what you’re doing when you change one variable at a time, holding all the other constant. So I find myself thinking: how can I learn only one of those things at a time?
Learning one thing at once
It’s obvious that there are two paths: learn Lisp syntax first and then add in the functional concepts; or learn functional concepts first and then learn the Lisp syntax. At first I thought that the syntax-first approach was impossible because there is no language with Lisp syntax but imperative idioms — until it hit me that that’s pretty much a description of Emacs Lisp. As Steve Yegge’s handy tutorial shows, the Lispiness of Emacs Lisp is rather skin deep, and it’s pretty unashamedly all about the side-effect.
So one approach for me to take would be to learn Emacs Lisp properly — properly enough to write a non-trivial extension, like for instance a major mode for editing ScottKit source files, and maybe for compiling and running the resulting games.
The opposite approach would be to take a language with actual syntax but which also supports functional concepts, and learn to use that functionally. Specifically, my idea is to do all the exercises from The Scheme Programming Language in Ruby, and only then to go back and do them in Scheme. And this of course is the the three-pass approach to reading the book that I alluded to earlier: once through with just the book; once more with the computer, learning functional programming by doing the exercises in Ruby; and then the third time, doing the exercises again but this time in Scheme. (A useful side-effect of such an approach would be that I’d discover the hard way where Ruby doesn’t have the necessary support for functional programming. But I know that it has closures, lambdas and continuations, and I don’t really see how list comprehensions are much different from a composition of closures, so I should be able to get some way before running into a brick wall.)
So far, I am not clear in my own mind what path I should take: learn to love Lisp syntax by writing Elisp? Learn functional programming by doing Scheme exercises in Ruby? Or just keep on ploughing through the book, continuing to do the Scheme exercises in Scheme, and hope that the syntax becomes tolerable as a side-effect while I am learning all the good stuff? (The second approach is appealing for the same reason that I found it satisfying, when I was at school, to win the music prize by playing Hadyn’s Trumpet Concerto on clarinet.)
Advice will be welcome at this point.
I do have one final point to make, but I’ve decided to save that for a separate article, because it’s fairly self-contained and potentially a bit controversial, and I don’t want comments on that one subject to overwhelm whatever useful advice I might get in response to this entry.
So — advise me!
Update (a few hours later)
As I continue to work my way through the exercises (using Scheme itself at least for now), I run into a problem: for exercise 2.9.3 I need set-car!, which the purity-fascist maintainers of PLT Scheme removed in release 4.0. No doubt there is a secret incantation to undo this change, but I can’t immediately see what it is. Can anyone point me to it?
I just hate this kind of thing. It’s like the absence of goto in Ruby. It’s very nice that language implementors give me “better” ways to do these things, but is it really too much to ask that they treat me like an adult and let me be the judge of how I want to write my own freakin’ code!? Language implementors, please remember these very wise words: mechanism, not policy.