This is the first Chapter of a series of posts about porting The Little Schemer to Clojure. You may wish to read the intro.
The big theme in Chapter 1 is Simplicity of Data. In The Little Schemer world there are two levels in the syntax that describes data – atoms and lists. Note that an atom in TLS refers to a a string of characters (perhaps you could consider this a list element – ie a non-list), wheras in Clojure an atom refers to a unit of a transaction.
To test for atoms – we’ll use the atom?
function:
(def atom? (fn [a] (not (seq? a))))
Note that we’re not using the defn
syntax in Clojure but def ... fn
. This is intentional because the book uses this standard, and then makes use of it later in the book when it passes anonymous functions around.
Speaking of null
– the original Scheme has a different concept of nil than what we see in Clojure today. To make Clojure act like the null
tests in the book – we use the null?
function:
(def null? (fn [a] (or (nil? a) (= () a))))
In TLS the thematic emphasis is also on the Simplicity of Syntax. All data structures are represented by lists, and lists of lists (ie nested listed). In Clojure we have several types of data structures – but the closest to what we’re dealing with here is the Sequence. (Now it’s more complex than that – because in Clojure a sequence is more like an iterator interface – but we’ll start there.)
The other concept to get in Chapter 1 is that lists are treated as stacks rather than arrays. We push elements on the end and pop them off. Our primitives to do this in TLS are cons
to push, car
to pop and cdr
to get the list remainder. In Clojure this corresponds to cons
to push, first
to pop and rest
to get the sequence remainder. (Note that Clojure also has conj
, but we’ll stick to sequences and using cons for now)
The last trick that Chapter 1 introduces are nested lists. The short summary of this is that if you push a list onto a list you get a nested list (using cons
). To reverse this then you pop the nested list off the list (using car/first
).
I don’t think it’s a good idea to implement scheme over clojure just to literally comply with the examples. It doesn’t help to learn clojure, it doesn’t help to learn scheme. A better way is to find semantic equivalents in the idiomatic clojure. For instance, lists and atoms become seqs and keywords, right?
@S
Fascinating question. I don’t believe the original book was ever intended to teach ‘The Scheme Language’ – but to teach just enough for the reader to get some big ideas in Computer science.
Is this a good way to learn Clojure? No (http://stackoverflow.com/questions/4030388/will-reading-the-little-lisper-help-me-in-learning-clojure).
The book is about deliberately steering away from using library functions, and instead builds up on a minimal set of primitives, in order to be able to build a lisp interpreter for that minimal set of primitives? (In addition to teaching recursion.) You can read more about this idea here(http://thelittlelisper.blogspot.com.au/2010/09/steve-yegge-how-many-primitives-does-it.html).
The short answer to your questions is “Yes there is a choice that could be made for either making it about learning Clojure or something else” I wanted to go for the y-combinator and the meta-circular evaluator using the simplest path (which I believe the book does too).
Your source code prints out the value of nil? applied to a few things but didn’t you want to demonstrate null? rather than nil??
Thanks for the feedback – this has been updated.
What’s the rationale of implement `atom?` with `seq?` instead of `coll?`?
`coll?` works for other basic Clojure data structures as well.
Hi Dong,
The reasoning was that rather than teaching Clojure, we wanted to work towards implementing a minimal LISP from scratch with a minimal set of operators – to do that – we need to mimic a single list data structure – and don’t need to work on others.
Cheers
JG
Hi Jules,
I love the motivation behind this reimplementation of tLS/L, but I’m concerned that Clojure’s departure from the use of proper cons cells renders the atom? example nonsensical.
To my understanding, atoms in languages like Common LISP are simply indivisible entries not formed by cons cell operations. In Clojure, however, such cons cells don’t exist.
Please correct me if I’m off here. I’m still fairly new and inexperienced in lisps, including Clojure.
Best,
David S
Thanks David, Agree that this is not idiomatic Clojure. I was aiming for a “dictionary style” transaction function for function to be as true to the book as possible, whilst still making use of Clojure.