Threading Macros
Prerequisites: map, filter, and reduce
Nested calls read inside out: (reduce + (map #(* % %) (filter even? nums))).
Threading macros let you write the same pipeline top to bottom.
->> (thread last)
(def nums [3 8 12 5 20 7])
(println (reduce + (map #(* % %) (filter even? nums))))
(println (->> nums
(filter even?)
(map #(* % %))
(reduce +)))
608
608
->> puts the result of each step as the last argument of the next.
Sequence functions take the collection last, so ->> is the one for
collection pipelines.
-> (thread first)
(def user {:name "ada" :visits 3})
(println (-> user
(assoc :role :admin)
(update :visits inc)
(update :name clojure.string/capitalize)))
(println (-> " Hello World " clojure.string/trim clojure.string/lower-case (str "!")))
{:name Ada, :visits 4, :role :admin}
hello world!
-> puts the result as the first argument. Map functions (assoc,
update, dissoc) and string functions take their main value first, so
-> fits them. A bare function name like clojure.string/trim works as
a step too.
Other threading macros
(def order {:items [{:price 10 :qty 2} {:price 5 :qty 1}] :coupon nil})
(println (some-> order :coupon clojure.string/upper-case)) ; stops at nil
(println (some-> order :items first :price (* 2)))
(println (cond-> {:total 25}
(> 25 20) (assoc :free-shipping true)
(:coupon order) (assoc :discount 5)))
(println (as-> [1 2 3] v
(conj v 4)
(map inc v)
(reduce + 0 v)))
nil
20
{:total 25, :free-shipping true}
14
some->/some->>stop and returnnilas soon as a step givesnil— safe navigation.cond->applies each step only when its test is truthy.as->names the value so you can put it anywhere in each step.
Practice
Rewrite (str/join ", " (map str/upper-case (filter #(> (count %) 3) words)))
with ->>, for ["tea" "coffee" "juice" "milk"].
Show solution
(require '[clojure.string :as str])
(def words ["tea" "coffee" "juice" "milk"])
(println (->> words
(filter #(> (count %) 3))
(map str/upper-case)
(str/join ", ")))
COFFEE, JUICE, MILK