I didn’t intend to wait a month between installments, but here we are. When we left off, we were discussing a couple of implementation os exponentiation, and how to print things out. We made it up to page 35.
Next, the book discusses the main Lisp data structure: the list. It discusses how Lisp assumes that in any list, the first item in that list is a command of some sort, and to change that behavior, you place a single quote,
', before the list. Clojure has the same expectation, so that if you enter this at a REPL:
(foo bar baz)
you will get the following error,
CompilerException java.lang.RuntimeException: Unable to resolve symbol: foo in this context...
indicating that it tried to execute that list as a function call, but failed to find a function called “foo”. Just as in Lisp, you can quote the list by prefixing it with a single quote. Thus, this
'(foo bar baz)
is now treated as data, instead of a function call. Similarly, this is a function call
(expt 2 3)
is a call to the expt function, while this
'(expt 2 3)
is a list with three elements.
Barski then goes into a length discussion about the list and its structure, specifically the cons cell. In Lisp, a cons cell is a pair of items, and its list is implemented as a linked list, with the second part of each cons cell linking to the next cons cell in the list. Clojure does not have a cons cell, and so does not implement its lists in terms of them. Lisp uses cons cells individually as simple pairs, too, and you can simulate this use by using a 2-element vector, like so
(def pair [:a :b])
Beginning on page 38, Barski starts discussing the various functions that operate on lists. I will provide the Clojure equivalent functions, where possible.
The cons Function
In Lisp, you create cons cells using the
cons function. The result is a cons cell with the two arguments set as the left and right portions of the cell, or just a left-side, if the right is
nil. You can simulate this, using the
(vector 'chicken 'cat)
or as a literal vector
Clojure does have a
cons function, but it functions somewhat differently than its Lisp counterpart. It prepends its first argument onto its second argument, which must be a list-like thing, returning the result as a list. So this in Lisp
(cons 'chicken 'cat)
can’t be done using Clojure’s cons function, since the second argument is not a list-like thing. If you try, you will get this error
IllegalArgumentException Don't know how to create ISeq from: clojure.lang.Symbol clojure.lang.RT.seqFrom (RT.java:505)
If you need to create a sequence of a chicken and a cat, use one of these:
(vector 'chicken 'cat) ['chicken 'cat] '(chicken cat)
Obviously, the first two create vectors, while the last one create a list.
In Lisp, the empty list and
nil are equivalent, but not so in Clojure. However, these two examples of consing a value and and empty list, and a value and nil, behave essentially the same, returning a list of just the first element.
(cons 'chicken 'nil) ; (chicken) (cons 'chicken ()) ; (chicken)
The next three examples function effectively the same, though remember that what you are getting back is not a linked list of cons cells, as you would in Lisp.
(cons 'pork '(beef chicken)) ; (pork beef chicken) (cons 'beef (cons 'chicken '())) ; (beef chicken) (cons 'pork (cons 'beef (cons 'chicken ()))) ; (pork beef chicken)
The car and cdr Functions
Lisp has the basic functions
cdr for accessing the first element of a lisp, and the remaining elements of a list, respectively. These names are directly related to the original hardware on which Lisp ran. Clojure does not have functions with these names, but it does provide its own functions that give the same results.
car, Clojure gives us
first, which actually is a better name for what it does. It works like this
(first '(pork beef chicken)) ; pork
cdr, Clojure actually has two functions,
rest. With a listy thing of 2+ elements, both these function behave the same, returning everything but the first element
(rest '(pork beef chicken)) ; (beef chicken) (next '(port beef chicken)) ; (beef chicken)
They differ, however, on what happens if there’s nothing after the first element, or the entire list is the empty list.
next will return
nil in both these cases, while
rest will return an empty list. As I said, in Lisp, these are the same thing, but in Clojure, they are very different. The empty list is truthy, while nil is not
(when nil :truthy) ; nil (when '() :truthy) ; :truthy
The c*r Functions
Lisp defines combinations of
cdr to allow you to get the second item, the third, the second item from the remainder of the remainder of a list, etc. Most CL implementations provide these functions up to four levels deep. Here’s a partial list to illustrate
(cadr '(pork beef chicken)) ; BEEF (cdar '((peas carrots tomatoes) (pork beef chicken))) ; (CARROTS TOMATOES) (cddr '((peas carrots tomatoes) (pork beef chicken) duck)) ; (DUCK) (caddr '((peas carrots tomatoes) (pork beef chicken) duck)) ; DUCK (cddar '((peas carrots tomatoes) (pork beef chicken) duck)) ; (TOMATOES) (cadadr '((peas carrots tomatoes) (pork beef chicken) duck)) ; BEEF
And let’s be honest, keeping all those ‘a’s and ‘d’s straight can be pretty confusing (at least, it is to me).
Since Clojure doesn’t have
cdr, it also lacks these functions. It does have one analog function, and that is for the CL function
cadr, which provides the car of the cdr of a list, also known as the second item. Thus, Clojure has
second that does the same thing,
(second '(pork beef chicken)) ; beef
but that’s it for built-ins. You can roll your own versions of these CL functions, but from what I’ve read, this is considered a code smell. Even though I don’t think you should try to implement these functions, let me show you how you might do it, if you wanted to.
Instead of explaining each one, and its possible implementation in Clojure, I’m just going to include the code for all of them in one, big blob.
(defn cdar [lst] (rest (first lst))) (cdar '((peas carrots tomatoes) (pork beef chicken))) ; (carrots tomatoes) (defn cddr [lst] (rest (rest lst))) (cddr '((peas carrots tomatoes) (pork beef chicken) duck)) ; (duck) (defn caddr [lst] (first (rest (rest lst)))) (caddr '((peas carrots tomatoes) (pork beef chicken) duck)) ; duck (defn cddar [lst] (rest (rest (first lst)))) (cddar '((peas carrots tomatoes) (pork beef chicken) duck)) ; (tomatoes) (defn cadadr [lst] (first (rest (first (rest lst))))) (cadadr '((peas carrots tomatoes) (pork beef chicken) duck)) ; beef
So, if you shouldn’t create analogous functions, how do you get at the elements you need? One way would be to use Clojure’s destructuring. I’m not going to go into a full explanation of how destructuring works, but suffice it to say that it’s a way to assign elements of a listy thing to individual locals.
Here’s how you could achieve the same results as
(let [[fst] '((peas carrots tomatoes) (pork beef chicken))] (rest fst)) ; (carrots tomatoes) ; instead of cdar
As you can see, this code assigns the first element
'(peas carrots tomatoes) to the local
fst. It then calls
rest on that, which returns the remainder of that list.
Here, then, is a code dump of destructuring implementation of the rest of the
c*r functions that I listed earlier.
(let [[fst] '((peas carrots tomatoes) (pork beef chicken))] (rest fst)) ; (carrots tomatoes) ; instead of cdar (let [[_ & rst] '((peas carrots tomatoes) (pork beef chicken) duck)] (rest rst)) ; (duck) ; instead of cddr (let [[_ & rst] '((peas carrots tomatoes) (pork beef chicken) duck) tail (rest rst)] (first tail)) ; duck ; instead of caddr (let [[fst] '((peas carrots tomatoes) (pork beef chicken) duck) tail (rest fst)] (rest tail)) ; (tomatoes) ; instead of cddar (let [[_ & rst] '((peas carrots tomatoes) (pork beef chicken) duck) fst (first rst) tail (rest fst)] (first tail)) ; beef ; instead of cadadr
That’s It, For Now
OK, that brings us to the end of chapter 3. I think this may be the longest post so far. I will move on to chapter 4, and will be back soon with the next installment.
As always, full code is available at https://github.com/joeygibson/lolclojure.