Skip to content
AgileLogics
5 min read

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 return nil as soon as a step gives nil — 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