Skip to content

Commit dfc78da

Browse files
committed
Add mapreduce_single function
Since the demise of `r_promote` in #22825, there is now a type-instability in `mapreduce` if the operator does not give an element of the same type as the input. This arose during my implementation of Kahan summation using a reduction operator, see: JuliaMath/KahanSummation.jl#7 This adds a `mapreduce_single` function which defines what the result should be in these cases.
1 parent c55e58f commit dfc78da

File tree

1 file changed

+35
-4
lines changed

1 file changed

+35
-4
lines changed

base/reduce.jl

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ function mapfoldl(f, op, itr)
6464
return Base.mapreduce_empty_iter(f, op, itr, iteratoreltype(itr))
6565
end
6666
(x, i) = next(itr, i)
67-
v0 = f(x)
67+
v0 = mapreduce_single(f, op, x)
6868
mapfoldl_impl(f, op, v0, itr, i)
6969
end
7070

@@ -133,7 +133,7 @@ function mapfoldr(f, op, itr)
133133
if isempty(itr)
134134
return Base.mapreduce_empty_iter(f, op, itr, iteratoreltype(itr))
135135
end
136-
return mapfoldr_impl(f, op, f(itr[i]), itr, i-1)
136+
return mapfoldr_impl(f, op, mapreduce_single(f, op, itr[i]), itr, i-1)
137137
end
138138

139139
"""
@@ -174,7 +174,7 @@ foldr(op, itr) = mapfoldr(identity, op, itr)
174174
@noinline function mapreduce_impl(f, op, A::AbstractArray, ifirst::Integer, ilast::Integer, blksize::Int)
175175
if ifirst == ilast
176176
@inbounds a1 = A[ifirst]
177-
return f(a1)
177+
return mapreduce_single(f, op, a1)
178178
elseif ifirst + blksize > ilast
179179
# sequential portion
180180
@inbounds a1 = A[ifirst]
@@ -238,12 +238,30 @@ pairwise_blocksize(::typeof(abs2), ::typeof(+)) = 4096
238238

239239
# handling empty arrays
240240
_empty_reduce_error() = throw(ArgumentError("reducing over an empty collection is not allowed"))
241+
242+
"""
243+
Base.reduce_empty(op, T)
244+
245+
The value to be returned when calling [`reduce`](@ref) with `op` over an empty array with
246+
element type of `T`.
247+
248+
If not defined, this will throw an `ArgumentError`.
249+
"""
241250
reduce_empty(op, T) = _empty_reduce_error()
242251
reduce_empty(::typeof(+), T) = zero(T)
243252
reduce_empty(::typeof(*), T) = one(T)
244253
reduce_empty(::typeof(&), ::Type{Bool}) = true
245254
reduce_empty(::typeof(|), ::Type{Bool}) = false
246255

256+
257+
"""
258+
Base.mapreduce_empty(f, op, T)
259+
260+
The value to be returned when calling [`mapreduce`](@ref) with `f` and `op` over an empty
261+
array with element type of `T`.
262+
263+
If not defined, this will throw an `ArgumentError`.
264+
"""
247265
mapreduce_empty(f, op, T) = _empty_reduce_error()
248266
mapreduce_empty(::typeof(identity), op, T) = reduce_empty(op, T)
249267
mapreduce_empty(f::_PromoteSysSizeFunction, op, T) =
@@ -266,6 +284,19 @@ mapreduce_empty_iter(f, op::typeof(&), itr, ::EltypeUnknown) = true
266284
mapreduce_empty_iter(f, op::typeof(|), itr, ::EltypeUnknown) = false
267285
mapreduce_empty_iter(f, op, itr, ::EltypeUnknown) = _empty_reduce_error()
268286

287+
# handling of single-element iterators
288+
"""
289+
Base.mapreduce_single(f, op, x)
290+
291+
The value to be returned when calling [`mapreduce`] with `f` and `op` over an iterator
292+
which contains a single element `x`.
293+
294+
The default is `f(x)`.
295+
"""
296+
mapreduce_single(f, op, x) = f(x)
297+
298+
299+
269300
_mapreduce(f, op, A::AbstractArray) = _mapreduce(f, op, IndexStyle(A), A)
270301

271302
function _mapreduce(f, op, ::IndexLinear, A::AbstractArray{T}) where T
@@ -275,7 +306,7 @@ function _mapreduce(f, op, ::IndexLinear, A::AbstractArray{T}) where T
275306
return mapreduce_empty(f, op, T)
276307
elseif n == 1
277308
@inbounds a1 = A[inds[1]]
278-
return f(a1)
309+
return mapreduce_single(f, op, a1)
279310
elseif n < 16 # process short array here, avoid mapreduce_impl() compilation
280311
@inbounds i = inds[1]
281312
@inbounds a1 = A[i]

0 commit comments

Comments
 (0)