Clojure Day Two


Haskell, But Quickly

Heads Up!

I'm writing a book about building and deploying web applications with Haskell and Yesod. Want to know when it's released? Click here.

The second day of Clojure looks at lazy evaluation, macros, and writing classes to interfaces. Macros are a core concern of all lisps, so understanding this idea means I would be slightly less disoriented when reading something in another dialect like Common Lisp or Scheme.

  1. Things to find
  2. Some Clojure macros
  3. A lazy sequence
  4. Things to do
  5. The unless condition
  6. A type with defrecord
  7. Thoughts

Things to find

Some Clojure macros

Find the implementation of some of the commonly used macros in the Clojure language.

A whole bunch of core Clojure constructs are in fact macros. We can view the implementation of a macro by expanding it. Here are a couple of examples I found in the documentation:

user=> (macroexpand '(when (pos? a) (println "positive") (/ b a)))
(if (pos? a) (do (println "positive") (/ b a)))

user=> (macroexpand '(-> {} (assoc :a 1) (assoc :b 2)))
(assoc (clojure.core/-> {} (assoc :a 1)) :b 2)')

A lazy sequence

Find an example of defining your own lazy sequence.

Clojure’s lazy evaluation is quite clever in that it allows you to define some infinite sequence, and rather than hang forever while it computes to infinity, you can just take the parts from the sequence that you need. I found a fairly contrived yet simple to understand example on Stack Overflow:

(defn ints-from [n]
  (cons n (lazy-seq (ints-from (inc n)))))

(take 10 (ints-from 7))
=> (7 8 9 10 11 12 13 14 15 16)

Things to do

The unless condition

Implement an unless with an else condition using macros.

With macros, we’re able to add our own control structures to Clojure. Defining a macro is similar to defining a function; the difference is that a normal function will evaluate all of its arguments, whereas with a macro we can delay execution.

Here’s my implementation of an unless control structure. I wouldn’t write this in production code, but it makes for a clear example.

(defmacro unless [test body]
  (list 'if (list 'not test)
  body
  '(println "Chocolate Chip Wookiee")))

; usage
user=> (unless false (println "Do or do not. There is no try."))
Do or do not. There is no try.
nil
user=> (unless true (println "It’s a trap!"))
Chocolate Chip Wookiee
nil

An interesting extension of this would be to declare an optional third parameter that takes another body, so the user can choose what they’d like to do if the else branch of the conditional is reached.

A type with defrecord

Write a type using defrecord that implements a protocol.

Records and protocols are Clojure’s equivalent of Java’s classes and interfaces. Thinking in terms of classes and interfaces seems like a very OO-style thing to do, so I’m unsure where this fits into a functional language like Clojure. I think Rich Hickey spent four years lying in a hammock thinking about how to design Clojure. I think I’m going to need four years hacking with Clojure to understand it.

(defprotocol Manoeuvre
  (accelerate [this]))

(defrecord Car [brand model]
  Manoeuvre
  (accelerate [_] (print "The " brand model " has accelerated.")))

; Usage
(def boxsterS (->Car "Porsche" "Boxster S"))

(accelerate boxsterS)

Thoughts

As I mentioned, Clojure is complicated and I think it’s not a language that can be skim-read easily. Given that it’s a functional language and it’s on the JVM, I’d say its a language that’s worth the initial time investment. I’ll make a mental note to write my Next Big Idea™ in Clojure.