Purpose of reduce(Array, Array)

Hello,
I’m wondering what the purpose of reduce is when given two arrays as only arguments. Here’s what I’ve observed:

julia> reduce([1], [4])
4
julia> reduce([1, 2, 3], [4])
4
julia> reduce([1, 2, 3], [1])
1
julia> reduce([1, 2, 3], [4, 5])
ERROR: MethodError: objects of type Array{Int64,1} are not callable
Use square brackets for indexing an Array.
Stacktrace:
[1] _mapreduce(::typeof(identity), ::Array{Int64,1}, ::IndexLinear, ::Array{Int64,1}) at ./reduce.jl:311
[2] _mapreduce_dim at ./reducedim.jl:305 [inlined]
[3] #mapreduce#538 at ./reducedim.jl:301 [inlined]
[4] mapreduce at ./reducedim.jl:301 [inlined]
[5] #reduce#539 at ./reducedim.jl:345 [inlined]
[6] reduce(::Array{Int64,1}, ::Array{Int64,1}) at ./reducedim.jl:345
[7] top-level scope at none:0

It seems to me that, given a singleton Array as the second argument, it returns the value in this array, otherwise, it throws a (rather uninformative) error. Apparently, the first argument doesn’t matter at all. However, I’ve been unable to find any documentation of this behavior.

If anyone can explain this, I would be most grateful.

PS. Upon further experimentation, it seems to throw an error if the first argument is empty. Also, it seems to have inherited this behavior from foldl. I’m still confused.

1 Like

Reduce takes two arguments — a function f and a collection A. The function f must take two arguments, and then reduce goes through the collection and one-element-at-a-time it updates result = f(result, elt). So if there are two elements in A, the result is:

f(A[1], A[2])

If there are three elements in A, the result is:

result = f(A[1], A[2])
result = f(result, A[3])

And so on.

But what happens if there’s only one element in A? Well, you can’t call f at all because it might not have an applicable one-argument method defined! Thus the function argument is ignored completely and we just take a shot in the dark and return the one element itself. If there are no elements in A, then there’s nothing we can do but error. In neither case if f even attempted to be called, and thus it can be anything at all.

2 Likes

Thank you, I think that makes sense. However, I don’t see why this is permitted in the first place. I don’t see why anyone would want to do this. Wouldn’t it be better to give reduce a type that disallowed this usage?

In general Julia code is not strictly typed. The reason for this is that it lets other people use your functions with their types, which sometimes produces really unexpected and useful results. An example are using numbers that track uncertainty with DifferentialEquations. No one designed for it to work, but by not strictly typing, it let someone try putting the two together and getting state of the art results.

2 Likes

It would, but any type can be made callable, so that it’s impossible to restrict the type of a callable argument in a generic way.

2 Likes

I also agree that this behaviour is quite confusing - it would make sense to raise an error when no init is given and the array has only one element. Currently it is not even type-stable, if my understanding is correct: the return type of reduce for a singleton array is the eltype of that array, but for a longer array it the the return type of f. I got caught by this once or twice, but mostly use reduce with init anyway.