21 September 2021

'Threading' a Pedagogical Needle (part 1)

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

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.