9

I am writing a custom transducer as an exercise, but I am surprised to see that its 0-arity init function is not called.

Why?

Is it related to which aggregation function I am using? If yes, which ones would call the init function and why others are not?

(defn inc-xf [xf]
  "inc-xf should be equivalent to (map inc)"
  (fn
    ;; init
    ([]
     (println "init") ;; <- this is not called (???)
     (xf))

    ;; step
    ([result input]
     (println "step" result input)
     (xf result (inc input)))

    ;; completion
    ([result]
     (println "completion" result)
     (xf result))))

(transduce inc-xf
           +
           100
           [5 5 5])
Vincent Cantin
  • 16,192
  • 2
  • 35
  • 57

1 Answers1

6

If you look at the implementation of transduce you can see what happens.

(defn transduce
  ;; To get the init value, (f) is used instead of ((xform f))
  ([xform f coll] (transduce xform f (f) coll))
  ([xform f init coll]
   ,,,))

Why, however, is more difficult to answer.

Transducers implementing the zero arity is part of the requirements for a transducer, but it is never actually used in any transducing context in clojure.core. On the mailing list there's been a post asking the same question as you and proposal of an implementation of transduce that actually uses the init arity. The jira ticket was declined, however, with the explanation:

Rich asked me to decline the ticket because the init arity of the xform should not be involved in the reducing function accumulation. - Alex Miller

Why, then, is the init arity part of the contract for a transducer if it's not used anywhere? ¯\_(ツ)_/¯

madstap
  • 1,552
  • 10
  • 21
  • I suppose one benefit of having transducers provide a (pass-through) init arity is to enable transforming reducing functions directly, for example `(r/fold (inc-xf +) coll)` … In any case many reducing functions have a 0-arity that provides some initial ‘identity’ value (`+`, `conj`) and a transducer must provide that same arity if it is to fulfil its general purpose of ‘transforming one reducing function to another’. – glts Feb 18 '18 at 20:55
  • Thank you for all this additional information. IMHO, for the implementor of a transducer, it is hard to do anything useful in the init 0-arity while not knowing for sure if it is going to be called. And if the transducer knows that information from the context, it is not fully decoupled. – Vincent Cantin Feb 19 '18 at 04:00
  • A note: I realised today that you mentioned `(f)` while the real question is about calling `(xform)`. – Vincent Cantin May 22 '18 at 01:22
  • @Vincent I'm not sure what you mean. I am referring to the fact that `(f)` is called in the current implementation, while I (and you judging by the question) would expect `((xform f))` to be called. Could you elaborate? – madstap May 22 '18 at 17:28
  • Sure. `(xform f)` and `(f)` have different purposes, `(xform something)` is meant to be called at the end of the transducing process. The question was about why the 0-arity `(xform)` was not called. – Vincent Cantin May 23 '18 at 05:45
  • I think we are talking past each other. When I say `xform` and `f` I mean `inc-xf` and `+` in your example. So `(+)` is called, but I'd expect `((inc-xf +))` to be called. – madstap May 25 '18 at 17:48