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:
- Use the lazy-seq function for the outermost sequence
- For consuming sequences, use the lazy rest not next (which is eager)
- Prefer HOFs
- 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.