The following example in clojure
calls the nullary and binary cases of +
in various ways:
(println 101 (+)) ; fine
(println 102 (+ (+) 4)) ; fine
(println 103 (reduce + (+) (range 4))) ; fine
(println 104 (reduce + (range 4))) ; fine
I tried replacing +
with mean-reducer
as described in this blog post (warning: no https).
I changed the mean-reducer
function to explicitly expose its identity element {:sum 0 :count 0}
when invoked with no arguments.
This works fine for the simple cases leading up to (reduce mean-reducer (range 4))
, but falls over for (reduce mean-reducer (range 4))
itself.
(defn mean-reducer
([] {:sum 0 :count 0})
([memo x]
{
:sum (+ x (memo :sum))
:count (inc (memo :count))
}))
(println 201 (mean-reducer)) ; fine
(println 202 (mean-reducer (mean-reducer) 4)) ; fine
(println 203 (reduce mean-reducer (mean-reducer) (range 4))) ; fine
;; (println 204 (reduce mean-reducer (range 4))) ; bad
runs and produces this with the last line commented.
% clojure mean_reducer.clj
201 {:sum 0, :count 0}
202 {:sum 4, :count 1}
203 {:sum 6, :count 4}
The error message and stack trace associated with the failed call to (reduce mean-reducer (range 4))
looks like this:
(~/clojure/mean_reducer.clj:12:62).
at clojure.lang.Compiler.load(Compiler.java:7647)
at clojure.lang.Compiler.loadFile(Compiler.java:7573)
at clojure.main$load_script.invokeStatic(main.clj:452)
at clojure.main$script_opt.invokeStatic(main.clj:512)
at clojure.main$script_opt.invoke(main.clj:507)
at clojure.main$main.invokeStatic(main.clj:598)
at clojure.main$main.doInvoke(main.clj:561)
at clojure.lang.RestFn.applyTo(RestFn.java:137)
at clojure.lang.Var.applyTo(Var.java:705)
at clojure.main.main(main.java:37)
Caused by: java.lang.ClassCastException: class java.lang.Long cannot be cast to class clojure.lang.IFn (java.lang.Long is in module java.base of loader 'bootstrap'; clojure.lang.IFn is in unnamed module of loader 'app')
at user$mean_reducer.invokeStatic(mean_reducer.clj:5)
at user$mean_reducer.invoke(mean_reducer.clj:1)
at clojure.lang.LongRange.reduce(LongRange.java:222)
at clojure.core$reduce.invokeStatic(core.clj:6823)
at clojure.core$reduce.invoke(core.clj:6810)
at user$eval143.invokeStatic(mean_reducer.clj:13)
at user$eval143.invoke(mean_reducer.clj:13)
at clojure.lang.Compiler.eval(Compiler.java:7176)
at clojure.lang.Compiler.load(Compiler.java:7635)
... 9 more
I think this means that somehow the lines are getting crossed and an element of (range 4)
is being bound to memo
, but I'm not sure why this would happen given that the case with an explicit initial element succeeds.
When you pass no initial value to reduce
the 2-arity version of mean-reducer
is invoked with the first two elements of the range i.e. (mean-reducer 0 1)
. From reduce
docs:
If val is not supplied, returns the result of applying f to the first 2 items in coll, then applying f to that result and the 3rd item, etc.
You'll need to supply an initial value to reduce
if you want to use mean-reducer
(and its 0-arity won't be used). reduce
has two different "contracts" for its 2-arity reducing function depending on whether an initial value is specified.
clojure.core.reducers/reduce
has exactly the behavior you want when no initial value is supplied:
When init is not provided, (f) is used.
(r/reduce mean-reducer (range 4))
=> {:sum 6, :count 4}