'Threading' a Pedagogical Needle (part 1)

IMHO, threading macros should be introduced started with `as->`.

September 21, 2021

One of the early mental hurdles that must be cleared when learning Clojure is the application of threading macros.

Usually the first macro we learn is either the thread-first (->) or thread-last (->>) macro. Sometime later, if you read the docs, you'll probably encounter the remaining threading macros:

The difficult thing about the thread-first and thread-last macros is that you have to mentally fill in the results being 'threaded' through the forms. Here's an interesting example from the thread-last docs:

;; An example of using the "thread-last" macro to get
;; the sum of the first 10 even squares.
(->> (range)
     (map #(* % %))
     (filter even?)
     (take 10)
     (reduce +))

The difficult thing for the beginner is to imagine the result of each form being 'threaded':

(->> (range)
     (map #(* % %) <result-of-range>)
     (filter even? <result-of-map>)
     (take 10 <result-of-filter>)
     (reduce + <result-of-take>))

If you simplify <result-of-*> (above) to something like $ you get this:

(->> (range)
     (map #(* % %) $)
     (filter even? $)
     (take 10 $)
     (reduce + $))

...which is remarkably similar to the exact mechanics of as->:

(as-> (range) $
      (map #(* % %) $)
      (filter even? $)
      (take 10 $)
      (reduce + $))

Here's an example of how I used as-> to prepare some hand-crafted html for a test assertion against the output from hiccup:

(let [tags (my-function-to-generate-tags)
      lines
      ["<html>                                                       "
       "  <head>                                                     "
       "  </head>                                                    "
       "  <body>                                                     "
       "    <h1>                                                     "
       "      Welcome!                                               "
       "    </h1>                                                    "
       "    <form action='/other/page' method='POST'>                "
       "      <label for='foo'>Enter Foo:</label>                    "
       "      <input id='foo' name='foo' type='number' value='3' />  "
       "      <input type='submit' value='Foo' />                    "
       "    </form>                                                  "
       "  </body>                                                    "
       "</html>                                                      "]]
  (should= [:html ...] tags)
  (as-> lines $
        (map string/trim $)
        (apply str $)
        (string/replace $ "'" "\"")
        (should= $ (hiccup/html tags))))

Conclusion

The as-> macro is much more flexible than -> or ->> and allows for easier visualization of what the macro is doing for you. Let's use it instead to introduce the concept of threading macros!

Part 2

But wait, there's more! Head over to part 2.

-Michael Whatcott