We’re given a list of users:

(def users
  [{:first "John" :last "Parsons" :active? true}
   {:first "Amanda" :last "Clark" :active? true}
   {:first "Kevin" :last "Lawrence" :active? false}
   {:first "Caroline" :last "Mills" :active? true}
   {:first "Warren" :last "Smith" :active? false}])

and we need to display the full names of the active users and return the result in a list, like so:

["John Parsons" "Amanda Clark" "Caroline Mills"]

For me, my first instinct in situations like this is to use ->> (thread last).

(->> users
     (filter #(:active? %))
     (map #(str (:first %) " " (:last %))))
;=> ["John Parsons" "Amanda Clark" "Caroline Mills"]

This works fine, but this way creates an extra lazy sequence after the first filter call. This can cause some performance issues if users is big or if we add more functions to thread last. We can solve this issue using transducers.

(def xform (comp (filter #(:active? %)) 
                 (map #(str (:first %) " " (:last %)))))

Functions like filter and map create a transducer when they are called with one argument. When they are combined using comp, the result is also a transducer.

In the snippet above, I composed the filter and map functions and set the resulting transducer to xform. I can apply xform to the list of users using the transduce function.

(transduce xform conj [] users)
;=> ["John Parsons" "Amanda Clark" "Caroline Mills"]

transduce takes the transducer we just created (xform), a function to apply on the collection (conj), an optional initial value ([]), and a collection (users). Transducers are flexible: if we wanted to return a string instead, we can swap out the reducing function and initial value to return what we need.

(transduce xform str "" users) 
;=> "John ParsonsAmanda ClarkCaroline Mills"

Since our original example returns the result in a collection, we can also apply our transducer to users using into.

(into [] xform users)
;=> ["John Parsons" "Amanda Clark" "Caroline Mills"]

I often come across similar problems in my day job as a ClojureScript/reagent developer. We’re given sequable data that needs to be filtered, modified, and formatted into neat React components for the user. Transducers can help performance by avoiding extra lazy sequences.

Additional Reading

Transducers - From the Clojure.org reference

Transducers are Coming - Rich Hickey can explain transducers and reducers better than I can

Edits:

June 13, 2021:

  • Added a missing parenthesis to the code example
  • Transducers help performance by avoiding extra iterations through the data and extra lazy sequences. -> Transducers can help performance by avoiding extra lazy sequences.

Thank you, Andrii!