Transducers: a deep dive

Road map

  • What are transducers
  • Use and create transducers
  • A review of map function

What is a Transducer?

(defn classic-sum [N]
    (let [all-numbers (range)
          input (take N all-numbers)
          times-two (map #(* 2 %) input)
          times-two-plus-three (map #(+ 3 %) times-two)]
        (reduce + 0 times-two-plus-three)))
(require '[clojure.core.async :as async])
(defn classic-sum-channel [N]
    (let [all-numbers (async/to-chan (range))
          input (async/take N all-numbers)
          times-two  (async/map< #(* 2 %) input)
          times-two-plus-three  (async/map< #(+ 3 %) times-two)]
    (async/reduce + 0 times-two-plus-three)))
(require '[clojure.core.async :as async])
(defn xform [N]         ;; transducers are usually named xform or xf
   (comp (take N)
         (map #(* 2 %))
         (map #(+ 3 %))))

(defn trans-classic-sum [N]
    (transduce (xform N) + 0 (range)))

(defn trans-classic-sum-channel [N]
    (async/transduce (xform N) + 0 (async/to-chan (range))))

1 : You start with some luggage crates

2 : unpack the luggaes from the crates

3 : remove bags that contain laptop

4 : label heavy bags

5 : load the airplane

Lessons from the analogy

How do we create transducers?

How do we use transducers?

(def xform (map #(* 2 %)))

(transduce xform + 0 (range 10))
;; 90

(eduction xform (range 10))
;; (0 2 4 6 8 10 12 14 16 18) ; an eduction
(def xform (map #(* 2 %)))

(into [1 2] xform (range 10))
;; [1 2 0 2 4 6 8 10 12 14 16 18]

(into (list 1 2) xform (range 10))
;; (18 16 14 12 10 8 6 4 2 0 1 2)

(sequence xform (range 10))
;; (0 2 4 6 8 10 12 14 16 18) ; lazy sec
(def xform (comp
                 (map #(* 2 %))
                 (map #(+ 3 %))))

(sequence xform (range 10))
;;  (3 5 7 9 11 13 15 17 19 21)
(def xform (map (comp 
                     #(* 2 %)
                     #(+ 3 %))))

(sequence xform (range 10))
;;  (6 8 10 12 14 16 18 20 22 24)

let's look at how we would implement the map function

def naive_map(fn, lst):
    ret = [0] * len(lst)
    for i in range(len(lst)):
        ret[i] = fn(lst[i])
    return ret
(defn my-map [f coll]
    (loop [ret [] coll coll]
        (if (empty? coll)
            ret
            (recur (conj ret (f (first coll))) (rest coll)))))
(conj [1 2 3] 99)
;; [1 2 3 99]
(conj '(1 2 3) 99)
;; (99 1 2 3)
(conj #{1 2 3} 99)
;; #{1 2 99 3}
(conj {:key1 1 :key2 2} [:key3 3])
;; {:key1 1, :key2 2, :key3 3}
(defn my-map [f coll]
    (reduce #(conj %1 (f %2)) [] coll))
(defn my-map [f coll]
    (reduce #(conj %1 (f %2)) [] coll))
(defn my-filter [pred coll]
    (reduce #(if (pred %2) (conj %1 %2) %1) [] coll))
(defn my-mapcat [pred coll]
    (reduce (fn [r x](reduce conj r (f x))) [] coll))
(defn my-map [f coll]
    (reduce #(conj %1 (f %2)) [] coll))
(defn my-filter [pred coll]
    (reduce #(if (pred %2) (conj %1 %2) %1) [] coll))
(defn my-mapcat [pred coll]
    (reduce (fn [r x](reduce conj r (f x))) [] coll))
(defn mapping [f]
    (fn [step]
        (fn [res x] (step res (f x)))))
(defn filtering [pred]
    (fn [step]
        (fn [res x] (if (pred x) (step res x) res))))
(def cat
    (fn [step]
        (fn [res x] (reduce step res x))))
(defn mapcatting [f]
    (comp cat (mapping f)))
(defn my-map [f coll]
    (reduce ((mapping f) conj) [] coll))
(defn my-filter [pred coll]
    (reduce ((filtering pred) conj) [] coll))
(defn my-mapcat [f coll]
    (reduce ((mapcatting f) conj) [] coll))
(defn mapping [f]
    (fn [step]
        (fn [res x] (step res (f x)))))
(def cat
    (fn [step]
        (fn [res x] (reduce step res x))))
(defn mapcatting [f]
    (comp cat (mapping f)))

for محمد بن موسی خوارزمی sake why is cat applied before map

(fn [res x] (reduce mapping-step res x))
(fn [res x] (reduce (fn [res x] (step res (f x))) res x))
(def trans-1
    (fn [step](fn [res x] (step res (+ 2 x)))))
(def trans-2
    (fn [step](fn [res x] (step res (* 3 x)))))
(def trans-3
    (comp trans-1 trans-2))
(fn [res x] (fn [res x] (step res (* 3 x))) res (+ 2 x)))
(defn take-while [pred]
    (fn [step]
         (fn [res x]
             (if (pred x)
                  (step res x)
                  (reduced res)))))
(defn take [n]
     (fn [step]
         (let [nv (volatile! n)]
             (fn [result input]
                 (let [n @nv
                       nn (vswap! nv dec)
                       result (if (pos? n) (step result input) result)]
        (if (not (pos? nn))
              (reduced result)
              result))))))
(defn map [f]
   (fn [rf]
       (fn
           ([] (rf))
           ([result] (rf result))
           ([result input]
               (rf result (f input))))))