The transducer halt-when throws an exception when used with into, but not with sequence.
(sequence (halt-when #{4}) (range)) ;; => (0 1 2 3)
(into [] (halt-when #{4}) (range))
;; class java.lang.Long cannot be cast to class clojure.lang.ITransientCollection (java.lang.Long is in module java.base of loader 'bootstrap'; clojure.lang.ITransientCollection is in unnamed module of loader 'app')
I think that is caused by a bug where it's returning input instead of result, and I think this implementation should fix that specific issue:
(defn halt-when1
([pred] (halt-when1 pred nil))
([pred retf]
(fn [rf]
(fn
([] (rf))
([result]
(if (and (map? result) (contains? result ::halt))
(::halt result)
(rf result)))
([result input]
(if (pred input)
(reduced {::halt (if retf (retf (rf result) input) (rf result))})
(rf result input)))))))
However, I think that the retf function is now coupled to the transducing context. So if you want to transform the result to, for example include the input that triggered the halt, then you need to know when you create the transducer that it will be used in a specific context and that you need to call conj, conj! or whatever. So I think that this signature of the function makes more sense:
(defn halt-when2
([pred] (halt-when2 pred nil))
([pred retf]
(fn [rf]
(fn
([] (rf))
([result]
(if (and (map? result) (contains? result ::halt))
(::halt result)
(rf result)))
([result input]
(if (pred input)
(let [r (if retf
(retf rf result input)
result)]
(reduced {::halt (rf r)}))
(rf result input)))))))
which changes the signature of the retf function to accept the reducing function as well as the result and the input, and now you get to actually decide what to do:
(into [] (halt-when2 #{4} (fn [rf r i] (rf r i))) (range)) ;; => [0 1 2 3 4]
Could maybe add a third arity to halt-when with some options map to tell it if we'd passed in a 2 arity retf or a 3 arity one or something? Does that make sense? Or maybe I'm asking for a new thing called stop-at or something?
--- edit ---
Just found this https://groups.google.com/g/clojure/c/6HvmJIUsXKk/m/gLqUsfcnAwAJ which I think explains more the reasoning behind returning the input, but I still think it's useful to provide a strategy for transforming the result without needing to know what the actual reducing function is.