Did you know about bound-fn?

Clojure provides a lot of different facilities to handle concurrency. One of these are Vars. They can be used to store thread-local information, eg. with binding. A different thread cannot see the value „modified“ with binding. And exactly there lies the rub…

A Guinea Pig

Let's start with some setup, which we can use later on.

(def *increment* 1)

(defn incr
  [x]
  (+ x *increment*))

This is basically a function similar to inc which increments a given number. Compared to inc it provides a knob by which we can change the increment step.

It works!

So. Now let's try our function.

user=> (map incr [1 2 3])
(2 3 4)

Ok. Nothing exciting so far. But now let's play with our knob. We use binding to change the value of *increment* temporarily.

user=> (binding [*increment* 3]
         (doall (map incr [1 2 3])))
(4 5 6)

Yeah! It works!

It works! … Really?

As I said in the introductory paragraph: Clojure provides several facilities to handle concurrency. Besides Vars there is for example pmap. pmap is basically a drop-in replacement for map which distributes the computation of the output seq on several threads. So each map can be easily turned to parallel construct by simply adding a single character. (Whether this makes sense in every case is a different question!)

So, let's try pmap for fun and profit!

user=> (binding [*increment* 3]
         (doall (pmap incr [1 2 3])))
(2 3 4)

Huh? Argh! We got caught by the thread-localness of Vars. The changed value of *increment* established with binding is not carried over to the new thread, where the actual computation is done. There the root binding is in effect and we get the original value of *increment*.

Remedies

To fix this situation we could take a hands-down approach and simply re-establish the binding in the target thread.

user=> (binding [*increment* 3]
         (doall (pmap (let [step *increment*]
                        (fn [x] (binding [*increment* step] (incr x))))
                      [1 2 3])))
(4 5 6)

This looks a bit silly, but imagine the binding being somewhere else in the code in a real-world project. So what do we do? We capture the thread-local binding in a local and pass a closure to pmap. This closure sets up the binding on the new thread before invocing incr.

This works but is quite a bit of tedious boilerplate. But luckily Clojure provides a helper utility for this scenario!

user=> (binding [*increment* 3]
         (doall (pmap (bound-fn [x] (incr x)) [1 2 3])))
(4 5 6)

bound-fn defines a function – similar to fn. However with the difference that it restores any thread-local bindings before executing the body of the function. So it is basically an extended approach to the above let solution.

In our case we can simplify things even more: we already have a function. We should be able to turn this function into a different one with the desired properties – similar to how memoize works. And in fact we can do that!

user=> (binding [*increment* 3]
         (doall (pmap (bound-fn* incr) [1 2 3])))
(4 5 6)

Upshot

A combination of Vars and binding can be a way to modify the environment of the code, eg. by rebinding *out*. However one has to take care when crossing thread boundaries. The bound-fn and bound-fn* utilities help in such a situation.

Notes

  • binding binds in parallel, while let binds sequentially.
  • The astute reader might have noticed several doalls. Why this is necessary is explained in a previous post.

Published by Meikel Brandmeyer on .