December 01, 2012

Closures in Clojure in five minutes

I created the following for a coaching excercise. If you step through the code one part at a time you actually may have learned something about closures and Clojure.

If you do the exercise, I suggest running it in Light Table. This is an IDE being built around the concept of live editing. That is, the code you're writing is running is you're writing it. So you get to immediately see what you're doing. (I set up the exercise for Light Table 0.1; there is an 0.2 out now; not sure if everything still works as expected.)

;; Function application
(+ 3 4)
(- 10 20)

;; Function definition
(defn add [a b]
  (+ a b))

(add 3 45)

;; Local binding
(let [i 0]
  i)

(let [i 0]
  (add i 1))

;; Everything is a constant.
;; For mutable state we have to use references.
;; Following shows how to declare and dereference a reference.
(let [i (ref 0)]
  (add (deref i) 1))

;; Updating of reference values requires transactions.
;; 'dosync' evaluates its argument inside of a transaction.
;; 'alter' takes a function which does the updating.
(let [i (ref 0)]
  (dosync (alter i (fn [x] (add x 1))))
  (deref i))

;; Functions are also values...
(defn inc [i] (add i 1))

;; ... and so can be passed as arguments...
(let [i (ref 0)]
  (dosync (alter i inc))
  (deref i))

;; ... and returned as values.
(defn foo []
  (let [i (ref 0)]
    (fn []
      (dosync (alter i inc))
      (deref i))))

;; Global bindings.
(def f (foo))
(def g (foo))

;; Add these as needed/wanted...
(f)
(g)

;; Congrats! You just used closures in Clojure.

Bonus points if you understand what the following piece of code is actually doing!

(defn new-calculator []
  (let [i (ref 1)]
    (fn [msg]
      ;; 'cond' is an if-elsif kinda thing...
      (cond
        (= msg "inc")
          (fn [d]
         (dosync
              (alter i
                     (fn [x] (+ x d))))
         (deref i))
        (= msg "dec")
          (fn [d]
         (dosync
              (alter i
                     (fn [x] (- x d))))
         (deref i))
       ))))

(def o (new-calculator))

((o "inc") 4)
((o "dec") 1)