beginners

Ramin Soltanzadeh 2026-04-13T16:51:45.312949Z

I now have a concrete simple example to illustrate the thing I don't really understand regarding the completion step of transducing.

(let [xform (comp (partition-by count)
                  (filter (constantly false)))
      coll  ["aa" "bb" "ccc"]]
  (transduce xform conj [] coll))
This results in [] as one would expect from everything being filtered out. What I don't understand is how this happens. The way I'm imagining the process I would have expected filter to do nothing for the final partition. I imagine the stateful partition-by to have ["ccc"] stored internally and return it at the final completion step where no filtering happens, resulting in [["ccc"]] instead of []. Obviously that is not what happens. Here's what I can figure out by reading the source code carefully:
;; exerpt from partition-by source code

(let [v (vec (.toArray a))]
  (.clear a)
  (let [ret (rf result v)]
    (when-not (reduced? ret)
      (.add a input))
    ret))
Upon reaching "ccc", we return (rf result v) which evaluates to (conj [] ["aa" "bb"]) or simply [["aa" "bb"]] and store ["ccc"] internally. As expected. Then we enter the completion step where some magic must happen that I fail to see. All I see is that we return (rf (unreduced (rf [] ["ccc"]))) . I think that should evaluate to [["ccc"]] that then gets passed down to the completion step of filter which does nothing. What am I missing? I'm sure I'm doing this all wrong, trying to imperatively follow the path of execution, but I'm not sure how else to approach it.

Ramin Soltanzadeh 2026-04-13T17:06:19.753629Z

I think I got it. The mistake is thinking that rf is conj . It's not. It's the filter-transducer-wrapped conj. I got confused by the left-to-right semantics of composing transducers. Obviously it's still actually right-to-left.

Ramin Soltanzadeh 2026-04-13T17:08:05.553789Z

So basically the way to design stateful transducers is to explicitly call the two-arity step function in the completion step to ensure that the flushed state also gets to play, as I understand it now.

dpsutton 2026-04-13T17:22:49.762469Z

the thing that most helped this click for me is > A transducer (sometimes referred to as xform or xf) is a transformation from one reducing function to another:

;; transducer signature
(whatever, input -> whatever) -> (whatever, input -> whatever)
https://clojure.org/reference/transducers Precisely right. it’s the comp form applied to conj. It must “go through” filter before conj ever sees it (or partition)

Ramin Soltanzadeh 2026-04-13T17:33:29.472269Z

Yeah. I wasn't thinking that partition-by actually calls filter (loosely speaking, of course). I was imagining that it "returns" and then filter gets called as the next step in the "pipeline" with the return value (at which point it would be too late).