Search code examples
clojuretransducer

What do I reduce this transducer with?


(defn multiply-xf
  []
  (fn [xf]
    (let [product (volatile! 1)]
      (fn
        ([] (xf))
        ([result]
         (xf result @product)
         (xf result))
        ([result input]
         (let [new-product (* input @product)]
           (vreset! product new-product)
           (if (zero? new-product)
             (do
               (println "reduced")
               (reduced ...)) <----- ???
             result)))))))  

This is a simple transducer which multiples numbers. I am wondering what would be the reduced value to allow early termination?

I've tried (transient []) but that means the transducer only works with vectors.


Solution

  • I'm assuming you want this transducer to produce a running product sequence and terminate early if the product reaches zero. Although in the example the reducing function xf is never called in the 2-arity step function, and it's called twice in the completion arity.

    (defn multiply-xf
      []
      (fn [rf]
        (let [product (volatile! 1)]
          (fn
            ([] (rf))
            ([result] (rf result))
            ([result input]
             (let [new-product (vswap! product * input)]
               (if (zero? new-product)
                 (reduced result)
                 (rf result new-product))))))))
    

    Notice for early termination, we don't care what result is. That's the responsibility of the reducing function rf a.k.a xf in your example. I also consolidated vreset!/@product with vswap!.

    (sequence (multiply-xf) [2 2 2 2 2])
    => (2 4 8 16 32)
    

    It will terminate if the running product reaches zero:

    (sequence (multiply-xf) [2 2 0 2 2])
    => (2 4)
    

    We can use transduce to sum the output. Here the reducing function is +, but your transducer doesn't need to know anything about that:

    (transduce (multiply-xf) + [2 2 2 2])
    => 30
    

    I've tried (transient []) but that means the transducer only works with vectors.

    This transducer also doesn't need to concern itself the type of sequence/collection it's given.

    (eduction (multiply-xf) (range 1 10))
    => (1 2 6 24 120 720 5040 40320 362880)
    (sequence (multiply-xf) '(2.0 2.0 0.5 2 1/2 2 0.5))
    => (2.0 4.0 2.0 4.0 2.0 4.0 2.0)
    (into #{} (multiply-xf) [2.0 2.0 0.5 2 1/2 2 0.5])
    => #{2.0 4.0}
    

    This can be done without transducers as well:

    (take-while (complement zero?) (reductions * [2 2 0 2 2]))
    => (2 4)