22 September 2021

'Threading' a Pedagogical Needle (part 2)

IMHO, let's introduce threading as a variation on the `let` form.

In part 1 I made the point that we should introduce threading macros by starting with as-> ('thread-as') instead of -> ('thread-first') or ->> ('thread-last'). Well, maybe it would be wise to back up even further and start with let.

As a reminder, here's the end goal:

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

But let's start off without using any threading operators. Rather, let's use let to label each step-by-step transformation of the data:

(let [numbers (range)
      squares (map #(* % %) numbers)
      evens   (filter even? squares)
      first10 (take 10 evens)
      sum     (reduce + first10)]
  sum)

Now let's take it one step toward a threading macro by rebinding the result of each step to a repeated symbol ($):

(let [$ (range)
      $ (map #(* % %) $)
      $ (filter even? $)
      $ (take 10 $)
      $ (reduce + $)]
  $)

Now I think we can slip in the 'thread-as' macro (the main hurdle here is reversing the order of $ (range)):

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

And now the 'thread-last' macro is quite accessible:

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

So, let, then as->, then ->>.