13 Jun 2021

Let and Binding macros in Clojure

Clojure has number special forms. Among them are let and binding that are implemented as macros. For new comers to Clojure, above can be confusing to understand, which I hope to address in this blog post. If you have read "Programming Clojure" book, you might be thinking that it is easy to use them correctly considering following straightforward example:

(def x 7) ;; bind the var x to the value 7
(defn print-x [x]
  (println x))

(let [x 9]
  (print-x)) ;; prints 7

(binding [x 9]
  (print-x)) ;; prints 9

Why? Because let creates a lexical scoped vars, which is local to the code block or functions where it is initially defined while def creates a global var that is visible to all functions under the namespace that you are in. Therefore, in the example above, let just created a new "x" with the value of 9. But it is not local to the function "print-x". That is to say, "print-x" have no knowledge of the local scoped binding that is created by let. binding macro instead creates a new binding for the already existed var, "x" with the value of 9. Since "print-x" is evaluated within the binding form, the newly bound value is visible to any chained calls within the form. All nice and easy. However, it is not that obvious to understand and use them correctly. Look at following example:

(def x 7)
(def y 9)

(let [x 5 y x] y) ;; returns 5
(binding [x 5 y x] y) ;; returns 7

let example is just fine. It created a new var "y" with the value of 5 bound to it. But what is going on with the binding example? If we dig little deeper to Clojure API doc, we learn that the new bindings in binding are made in parallel, not sequential as it is in let. Therefore, existing var "y" gets bound to the root binding of "x" not the overshadowed value of "x", which is the case in the let example. OK, so far so good. Let us look at another example:

(defn print-x [& args]
  (println "print-x prints: " args))
(defn print-y [& args]
  (println "print-y prints: " args))
(let [print-x print-y] (print-x 123)) ;; print-y prints: (1 2 3)
(binding [print-x print-y] (print-x 123)) ;; print-y prints: (1 2 3)
(binding [print-x print-y] #(print-x 123)) ;; prints
					     ;; cryptic "#<user$eval__28$fn__30
					     ;; user$eval__28$fn__30@bdb503>"
((binding [print-x print-y] #(print-x 123))) ;; print-x prints: (1 2 3)

All the lines but the last two are pretty straight forward. But how about the last line? Shouldn't it call "print-y" function? Not really. It is because the anonymous function is evaluated outside of the binding form. The "#<user$eval_28$fn_30 user$eval_28$fn_30@bdb503>" is an indication that anonymous function did not get evaluated. The extra parens evals above form. Ok, now, how do you make sure it evaluates within the binding form? Just enclose the anonymous function within parenthesis. What do we learn from all of the above? Here are the summary that helps one use let and binding correctly:

Tags: clojure