Monday, March 19, 2012

On Lisp in Clojure ch 3

I am continuing to translate the examples from On Lisp by Paul Graham into Clojure. Today I am looking at chapter 3.

Section 3.1 Functional Design

Graham shows an example of a bad-reverse function, which modifies the list it was passed and then shows a good-reverse which returns a new list. I am not going to translate the bad example, because it is even uglier in Clojure, where you have to go out of your way to mutate things.

(defn good-reverse [lst]
  (loop [lst lst acc ()]
    (if (= () lst)
      (recur (rest lst) (cons (first lst) acc )))))

(good-reverse [1 2 3 4])

The next bunch of examples talk about different Lisp functions that alter the values of their parameters. None of the examples really make sense in Clojure. We will talk more about that in section 3.3.

The truncate example was interesting. I am not sure why you would have a function that returns multiple scalars in a list based language. Why not return a list? Clojure does not have a truncate function, but you can use quot and rem to get the same effect.

(defn split-decimal [n]
  {:quotient (quot n 1)  :remainder (rem n 1) :quotent-int (int n)})

(split-decimal 26.235)
(split-decimal 26.235M)

The split-decimal function returns a map that contains 2 floats and an int when called with a float, and 2 decimals and an int when called with a decimal.

Section 3.2 Imperative Outside-In

To square a number we can either use Java's Math class or Clojure's expt function. Java's Math.pow returns a double:

(defn fun [x]
  (list 'a (Math/pow (first x) 2)))

The exponent function in Clojure is stored in a library called math.numeric-tower. It will return an int when passed two ints, and return a double when one of the parameters is a double.

To get math.numeric tower, you need to add a dependency to the project.clj file. After the add, the dependency section in my project.clj looks like:

:dependencies [[org.clojure/clojure "1.3.0"]
               [org.clojure/math.numeric-tower "0.0.1"]]

You import this dependency by typing lein deps at the command line. (Assuming you are using Leiningen, and why wouldn't you?)

The functional version of his example is:

(use '[clojure.math.numeric-tower])

(defn fun [x]
  (list 'a (expt (first x) 2)))

I thought the imperative version of the function was extra awkward, probably because in CLISP let does not bind sequentially. In Clojure where let does bind sequentially, and mutating a variable takes extra work, it makes sense to assign your variables when you declare them. That is how I would do it in C# too, so I think it is a reasonable imperative idiom.

(defn imp [x]
  (let [y (first x) sqr (expt y 2)]
    (list 'a sqr)))

Section 3.3 Functional Interfaces

This section makes Clojure look like a better Lisp than Lisp. The first example shows a call to a function nconc, which joins lists together and then puts the result into the first list. His function manages to work in a good functional style because he makes a copy of the list he is passed in, and passes the copy to the nconc function. By default Clojure doesn't mutate data in place it returns a new copy.

(defn qualify [expr]
  (cons 'maybe expr))

(qualify '(this is true))

Using cons puts the 'maybe at the front of either a list or a vector. If you rewrite the function using into, the 'maybe will get added to the front of a list or the end of a vector.

(defn qualify [expr]
    (into expr '(maybe)))

(qualify ['this 'is 'true'])

The next example is of calling a function that increments a variable and returns the new value. Again, Clojure does by default what Graham is advocating. The difference is, in Clojure Graham's right way is also the easy way.

;; x is immutable
(let [x 0]
  (defn total [y]
    (+ x y)))

;; x is mutable
(let [x (atom 0)]
  (defn total [y]
    (swap! x #(+ % y))))

Clojure makes mutable state available when you need it, but mutability always takes more work than immutability. What's more, if you have something that you want to be mutable, you have to declare it as a mutable type (here we use an atom). When you are using values instead of variables, you have a guarantee that no other function can change that. You only have to worry about mutation in things that are mutable, which are either Clojure's mutable structures or mutable Java objects when doing interop.

Rather than go through the rest of the examples in this section, I will just take comfort that Clojure encourages me to do things in a way Graham is advocating.

Section 3.4 doesn't have any examples. It does give a good explanation of some benefits of functional programming though.

And that concludes chapter 3.


  1. Do you need any help with this effort?

  2. octopusgrabbus: thanks for your offer. I am learning even more than I expected to by working through the book. My goal is not so much to produce translated examples in Clojure, but to learn Clojure, and to develop a Lispy style of thinking by translating the examples.

    Hopefully people can learn something from my posts. Anyone who can spend the time would learn much more by doing the translations for themselves.

    If you want to produce another set of examples, by all means do so. I recently discovered that Michael Fogus already posted translations of chapters 2 - 5 on his blog. I will be reading them closely, and I would love to have your examples to read as well.


  3. Hi Rick,

    Some exprs caught my attention:

    1/ Use (partial + y) for #(+ % y). I believe it makes the code easier to read and comprehend (not sure how much it influences being more idiomatic or not).

    2/ Use (empty? lst) for (= () lst). Again, it's more readable.

    Correct me if you think I'm wrong. I'm also on my way to Clojure nirvana :)

    I'm at and you're welcome to have your voice there.

    1. Hi Jacek,

      Thanks, those are both good changes. Partial is something that hasn't made it into my Clojure vocabulary yet, but your example looks great to me. I will try to take that into account going forward.

      Of course you are right, empty? is better. (= () lst) will never appear in my code again.

      Thanks again for the great comments,