Clojure: Set for a Lazy Life

I'm now reaching Pt. III of "The Joy of Clojure". The first part is about immutability and lazy evaluation.

Recap: Benefits of Immutability

Values are set in constructors and don't change thereafter. Makes it easier to debug and reason about programs. Implementing invariants is easier: invariants only need to be checked at creation time. Equality between objects becomes meaningful -- as the book so nicely puts it: "Equality in the face of mutability and concurrency is utter lunacy". Sharing can be done by passing references without headaches. Immutable objects are always thread-safe.

Laziness

Clojure is a partly lazy language. Usually, it will default to evaluate expressions eagerly, ie. as soon as program flow reaches the respective code. By contrast, a lazy language will only evaluate an expression if a value is actually needed. This can help save cycles, and is necessary for dealing with potentially infinite structures.

A simple example of lazy evaluation is short circuiting of conditions, to save on unnecessary computation and make constructs like this possible (Python):

if x is not None and x.a == 123:
    print "foo"

This would result in an exception for the case where x is None, since it would evaluate all expressions of the condition. But Python, like many other languages, will not evaluate x.a above if x is None, so it doesn't.

Clojure has the and macro which can do something similar:

user> (and true 1 (do (prn "foo") :alltruthy))
"foo"
:alltruthy
user> (and true false (do (prn "foo") :alltruthy))
false

Making lazy sequences

Four easy steps:

  1. Use the lazy-seq function for the outermost sequence
  2. For consuming sequences, use the lazy rest not next (which is eager)
  3. Prefer HOFs
  4. Don't hold onto the sequence head, this would prevent GC

The laz-seq function turns an expr into a seq-like object whose elements will only be realized when actually needed, and which will also cache it's elements.

Testing the caching properties of lazy-seq:

user> (defn sleepy-seq [] (Thread/sleep 1000) [1 2 3])
#'user/sleepy-seq
user> (def lazysleep (lazy-seq (sleepy-seq)))
#'user/lazysleep
user> lazysleep
(1 2 3) ; after some pause
user> lazysleep
(1 2 3) ; immediately

A bit of infinity

The iterate function produces an infinite sequence of the form x, f(x), f(f(x)), ... by iteratively applying a function to a starting point. Eg. produce powers of two (first 5):

user> (take 5 (iterate #(* % 2) 2))
(2 4 8 16 32)

The (repeat x), (repeat n x) function simply repeats one element. The cycle function repeats a sequence.

Delay and Force

The delay and force functions are mechanisms that implement a "call-by-need" behaviour. Essentially delay wraps an expression and force executes it. Also, delay will cache. Similar test like the one above:

user> (defn sleepy-seq [] (Thread/sleep 1000) (prn "awake!") [1 2 3])
#'user/sleepy-seq
user> (def delayed (delay (sleepy-seq)))
#'user/delayed
user> delayed
#<Delay@25e0cbd8: :pending>
user> (force delayed)
"awake!" ; after a bit of delay we see a side effect
[1 2 3]
user> (force delayed)
[1 2 3] ; no delay and no side effect

Those functions however are rather low-level. Usually it's more convenient to build on the lazy sequences above.

Aside: if-let

Shorthand -- assign a var if it's truty:

user> (if-let [r 1] (prn r))
1
nil
user> (if-let [r false] (prn r))
nil

This concludes chapter 6 on immutability and laziness. Next: more functional programming.

 · 
peter