Switch branches/tags
Nothing to show
Find file History
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Failed to load latest commit information.


#-> and ->>

Episode 005

In episode 2 we were discussing, I wrote the following expression:

Note:  Show threaded assoc!

Today I'd like to discuss this symbol right here

Note:  Highlite thread-first

This is an example of an expression threading macro. The term expression threading describes taking one s-expression and threading it through a second one. It has nothing to do with concurrency or traditional multithreading.

#Thread-first In order to understand expression threading, let's take a look at a very simple function definition. Here we have a function designed to convert Celsius to Fahrenheit.

episode-005=>(defn c-to-f [c]
  (* (+ c 32) 1.8))

As you can see it's a very simple function definition, with nested arithmetic operations. Let's transform this using the thread-first macro. Before we do that, let's use macro expansion to understand exactly what -> does.

episode-005=>(macroexpand '(-> c (+ 32)))
(+ c 32)

What thread-first does is take the first expression and inserts it in the second spot of the second macro. In our case, it takes c and inserts it in the second spot of (+ 32). This produced the output of (+ c 32). Now, I just said that thread-first takes the first expression and inserts in into the second expression.

episode-005=>(macroexpand '(-> (+ c 32) (* 1.8)))
(* (+ c 32) 1.8)

This is the exact body of the function we wrote earlier. This is good, but in its present form, thread-first hasn't bought us anything yet. Fortunately, there's more to this macro. Thread-first is variadic. Let's break this up and see what happens

episode-005=>(macroexpand '(-> c (+ 32) (* 1.8)))
(* (clojure.core/-> c (+ 32)) 1.8)

What happened here? A recursive call to thread first was inserted as the second argument in the last list. If we use macroexpand-all from clojure.walk, we can fully expand the expression.

episode-005=>  (macroexpand-all '(-> c (+ 32) (* 1.8)))
(* (+ c 32) 1.8)

Now, we finally have a simple call to thread first that produces the exact form we originally had. We can create a second function defined using this macro

episode-005=>(defn c->f [c] (-> c (+ 32) (* 1.8)))

Awesome! Now, there's only one thing left to do. Test our new function to see if it behaves properly. We know that 0 degrees C is equal to 32 degrees F, so let's make sure this works.

episode-005=>(c->f 0)

Uh oh. This isn't right. What went wrong? I'll tell you what went wrong. There's a bug in our original function.

episode-005=>(c-to-f 0)

It turns out I made a mistake in my algebra. Let's correct this in the original version. We need to switch an operator here... two constants there... make sure everything is in the right spot. Okay. Should be good to go.

Note:  Correct Fn

episode-005=>(c-to-f 0)

Now, let's correct the version written with the thread-first macro. All we have to do is change the order of these expressions in the macro call. Done.

Note: Correct other Fn

episode-005=>(c->f 0)

This use of thread-first is very idiomatic Clojure. As you can see, it lets us rapidly and confidently rearrange the code to get the right result. This fits into the Clojure way of testing pure functions and iterating to a new solution very, very well.

#Thread-last Thread first been a very successful tool in Clojure, and I believe the lisp world in general for a while. Introduced in Clojure 1.1 is a similar macro, thread last. Thread last is very similar to thread first, except that it inserts the expression into the end of the form. Let's take a look at some sample code to see this in action.

Here we have an alternate way to find the nth square number. As you may recall, the sum of the first n odd numbers is equal to n squared.

episode-005=>(defn square [n]
  (reduce +
	  (filter odd?
		  (range 0
			 (* 2 n)))))

Notice how the function drags right. We can clean up this code by using thread last.

episode-005=>(defn square->> [n]
  (->> n
       (* 2)
       (range 0)
       (filter odd?)
       (reduce +)))

And, as you can see both functions work just fine.

episode-005=>(square 10)

episode-005=>(square->> 10)

There's one last point about the threading macros I'd like to stress. At first glance, the end result only seems to be that it made our code easier to read. However, that misses a very subtle point. These macros actually abstracted away the process of building up a very specific nested list.
This process of abstracting away code construction is the real point of these tools, not improving code readability. However, that's a point we'll discuss in detail another day.

Anyway, that's how you use thread-first and thread-last. I hope this gave you a better understanding of these macros, and it is now easier for you to read code other Clojurians write. Thanks for stopping by today. I'm Sean Devlin, and this is Full Disclojure.