Skip to content

Instantly share code, notes, and snippets.

@xfthhxk
Last active August 27, 2024 19:29
Show Gist options
  • Save xfthhxk/99a38340c5d0e36331a2b29ffcc93157 to your computer and use it in GitHub Desktop.
Save xfthhxk/99a38340c5d0e36331a2b29ffcc93157 to your computer and use it in GitHub Desktop.
Clojure Transducer Notes
;; Transducer a portmanteau of transform and reducer?
(defn reducing-fn
([]
;; this 0 arity is called when no initial coll is provided to a transducer
;; This should return the starting state
(transient #{}))
([ans]
;; this arity is called with the accumulator/answer after all items in the
;; collection have been iterated over. This is the completion step of the
;; transducing process. The clojure.core/completing fn facilitates this
;; step for a fn that only has 0 and 2 arities.
(into (sorted-set) (persistent! ans)))
([ans x]
;; this arity is called with the accumulator and each item in the input collection
;; after it has been through the xform
(conj! ans x)))
(transduce (comp (remove even?)
(map #(* % %)))
reducing-fn
(range 10)) ;; => #{1 9 25 49 81}
----
;; eduction vs seqs
(defn my-inc
[x]
(println "my-inc: " x)
(inc x))
(def inc-res (->> (range 5)
(filter identity)
(map my-inc)))
(reduce + inc-res) ;; => 15 & side effects include printlns
;; my-inc: 0
;; my-inc: 1
;; my-inc: 2
;; my-inc: 3
;; my-inc: 4
;; no matter how many times you evaluate (reduce + inc-res), the printlns will print only once
;; This is because inc-res seq is realized at some point and does not need to be evaluated every time it is used
;; Of course, for a large seq you could run out of memory
;;-----------------------------------------
;; Eduction
;;-----------------------------------------
(def inc-educ
(eduction (filter identity) (map my-inc) (range 5)))
(reduce + inc-educ) ;; => 15 and also side effects each time this form is evaluated
;; Eduction does not cache the resulting seq and hence is memory efficient.
;; As a result, each time `(reduce + inc-educ)` is called the side effects will happen
;; ie evaluate `(reduce + inc-educ)` twice you'll see
;; my-inc: 0
;; my-inc: 1
;; my-inc: 2
;; my-inc: 3
;; my-inc: 4
;; my-inc: 0
;; my-inc: 1
;; my-inc: 2
;; my-inc: 3
;; my-inc: 4
;;------------------------
;; completing
;;------------------------
;; The completing function is needed only when you have a 2 arity fn
;; or want to modify the behavior of an existing fn to handle the
;; 1 arity case specially.
(fn
([] ,,,)
([ans x] ,,,))
;; and you need to simply add the 1 arity version
(fn [ans] ,,,)
(defn not-empty-kws
[xs]
(transduce (comp (map str/lower-case)
(map keyword))
(completing conj not-empty)
#{}
xs))
(not-empty-kws ["a" "b"]) ;; => #{:b :a}
;; instead of #{} you get nil in this case
(not-empty-kws []) ;; => nil
;;--------------------------------------------------------------------
;; Samples
;;--------------------------------------------------------------------
(->> [{:ids #{1 2}} {:ids #{3}}]
(mapcat :ids)
set)
(transduce (map :ids)
into
#{}
[{:ids #{1 2}} {:ids #{3}}])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment