From f36ffbb14e7c243dfbc1d54cc6efc922508f9b3f Mon Sep 17 00:00:00 2001 From: Matt Bauman Date: Tue, 26 Mar 2024 18:30:21 -0400 Subject: [PATCH 01/18] Add more clarity to `init`'s role in reduce and mapreduce --- base/reduce.jl | 54 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/base/reduce.jl b/base/reduce.jl index 6a0d46c61fcd9..1d85e621a619d 100644 --- a/base/reduce.jl +++ b/base/reduce.jl @@ -281,15 +281,29 @@ mapreduce_impl(f, op, A::AbstractArrayOrBroadcasted, ifirst::Integer, ilast::Int mapreduce(f, op, itrs...; [init]) Apply function `f` to each element(s) in `itrs`, and then reduce the result using the binary -function `op`. If provided, `init` must be a neutral element for `op` that will be returned -for empty collections. It is unspecified whether `init` is used for non-empty collections. -In general, it will be necessary to provide `init` to work with empty collections. +function `op`. + +The order of function evaluations and the associativity of the reduction is unspecified. Some +implementations may reuse the return value of `f` for elements that appear multiple times in +`itr`. Use [`mapfoldl`](@ref) or [`mapfoldr`](@ref) for +strict left or right associativity and guaranteed invocation of `f` for every value. + +If provided, `init` serves as the return value for empty `itrs`. For non-empty iterators, +it is included in the reduction exactly once and ensures that every element in `itrs` is +used as an argument to `op`. Like the reduction itself, the exact order and associativity +of how `init` is included is not specified. It is generally an error to call `mapreduce` +with empty collections without specifying an `init` value, but in unambiguous cases the +identity value may be returned; see [`Base.reduce_empty`](@ref) for more details. [`mapreduce`](@ref) is functionally equivalent to calling -`reduce(op, map(f, itr); init=init)`, but will in general execute faster since no +`reduce(op, map(f, itrs...)...; init=init)`, but will in general execute faster since no intermediate collection needs to be created. See documentation for [`reduce`](@ref) and [`map`](@ref). +Some commonly-used operators may have special implementations of a mapped reduction, and +should be used instead: [`maximum`](@ref)`(itr)`, [`minimum`](@ref)`(itr)`, [`sum`](@ref)`(itr)`, +[`prod`](@ref)`(itr)`, [`any`](@ref)`(itr)`, [`all`](@ref)`(itr)`. + !!! compat "Julia 1.2" `mapreduce` with multiple iterators requires Julia 1.2 or later. @@ -298,11 +312,6 @@ intermediate collection needs to be created. See documentation for [`reduce`](@r julia> mapreduce(x->x^2, +, [1:3;]) # == 1 + 4 + 9 14 ``` - -The associativity of the reduction is implementation-dependent. Additionally, some -implementations may reuse the return value of `f` for elements that appear multiple times in -`itr`. Use [`mapfoldl`](@ref) or [`mapfoldr`](@ref) instead for -guaranteed left or right associativity and invocation of `f` for every value. """ mapreduce(f, op, itr; kw...) = mapfoldl(f, op, itr; kw...) mapreduce(f, op, itrs...; kw...) = reduce(op, Generator(f, itrs...); kw...) @@ -452,13 +461,20 @@ _mapreduce(f, op, ::IndexCartesian, A::AbstractArrayOrBroadcasted) = mapfoldl(f, """ reduce(op, itr; [init]) -Reduce the given collection `itr` with the given binary operator `op`. If provided, the -initial value `init` must be a neutral element for `op` that will be returned for empty -collections. It is unspecified whether `init` is used for non-empty collections. +Reduce the given collection `itr` with the given binary operator `op`. + +The order of evaluations and the associativity of the reduction is unspecified. +This means that you shouldn't +use non-associative operations like `-` because it is undefined whether `reduce(-,[1,2,3])` +will be evaluated as `(1-2)-3` or `1-(2-3)`. Use +[`foldl`](@ref) or [`foldr`](@ref) for guaranteed left or right associativity. -For empty collections, providing `init` will be necessary, except for some special cases -(e.g. when `op` is one of `+`, `*`, `max`, `min`, `&`, `|`) when Julia can determine the -neutral element of `op`. +If provided, `init` serves as the return value for empty `itrs`. For non-empty iterators, +it is included in the reduction exactly once and ensures that every element in `itrs` is +used as an argument to `op`. Like the reduction itself, the exact order and associativity +of how `init` is included is not specified. It is generally an error to call `mapreduce` +with empty collections without specifying an `init` value, but in unambiguous cases the +identity value may be returned; see [`Base.reduce_empty`](@ref) for more details. Reductions for certain commonly-used operators may have special implementations, and should be used instead: [`maximum`](@ref)`(itr)`, [`minimum`](@ref)`(itr)`, [`sum`](@ref)`(itr)`, @@ -466,14 +482,8 @@ should be used instead: [`maximum`](@ref)`(itr)`, [`minimum`](@ref)`(itr)`, [`su There are efficient methods for concatenating certain arrays of arrays by calling `reduce(`[`vcat`](@ref)`, arr)` or `reduce(`[`hcat`](@ref)`, arr)`. -The associativity of the reduction is implementation dependent. This means that you can't -use non-associative operations like `-` because it is undefined whether `reduce(-,[1,2,3])` -should be evaluated as `(1-2)-3` or `1-(2-3)`. Use [`foldl`](@ref) or -[`foldr`](@ref) instead for guaranteed left or right associativity. - Some operations accumulate error. Parallelism will be easier if the reduction can be -executed in groups. Future versions of Julia might change the algorithm. Note that the -elements are not reordered if you use an ordered collection. +executed in groups. Future versions of Julia might change the algorithm. # Examples ```jldoctest From 32e762b13bf9d263e67c31ed65e551c01c0e4397 Mon Sep 17 00:00:00 2001 From: Matt Bauman Date: Wed, 27 Mar 2024 11:35:47 -0400 Subject: [PATCH 02/18] fix too many dots...... Co-authored-by: mikmoore <95002244+mikmoore@users.noreply.github.com> --- base/reduce.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/reduce.jl b/base/reduce.jl index 1d85e621a619d..9e6d1c0b443ed 100644 --- a/base/reduce.jl +++ b/base/reduce.jl @@ -296,7 +296,7 @@ with empty collections without specifying an `init` value, but in unambiguous ca identity value may be returned; see [`Base.reduce_empty`](@ref) for more details. [`mapreduce`](@ref) is functionally equivalent to calling -`reduce(op, map(f, itrs...)...; init=init)`, but will in general execute faster since no +`reduce(op, map(f, itrs...); init=init)`, but will in general execute faster since no intermediate collection needs to be created. See documentation for [`reduce`](@ref) and [`map`](@ref). From 549ceac67e4794d2c2631ddb95141d1135d70707 Mon Sep 17 00:00:00 2001 From: Matt Bauman Date: Wed, 27 Mar 2024 17:40:20 -0400 Subject: [PATCH 03/18] Incorporate feedback; define left-most position for init --- base/reduce.jl | 116 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 75 insertions(+), 41 deletions(-) diff --git a/base/reduce.jl b/base/reduce.jl index 9e6d1c0b443ed..3e997ad2b7fcc 100644 --- a/base/reduce.jl +++ b/base/reduce.jl @@ -280,20 +280,33 @@ mapreduce_impl(f, op, A::AbstractArrayOrBroadcasted, ifirst::Integer, ilast::Int """ mapreduce(f, op, itrs...; [init]) -Apply function `f` to each element(s) in `itrs`, and then reduce the result using the binary -function `op`. - -The order of function evaluations and the associativity of the reduction is unspecified. Some -implementations may reuse the return value of `f` for elements that appear multiple times in -`itr`. Use [`mapfoldl`](@ref) or [`mapfoldr`](@ref) for -strict left or right associativity and guaranteed invocation of `f` for every value. - -If provided, `init` serves as the return value for empty `itrs`. For non-empty iterators, -it is included in the reduction exactly once and ensures that every element in `itrs` is -used as an argument to `op`. Like the reduction itself, the exact order and associativity -of how `init` is included is not specified. It is generally an error to call `mapreduce` -with empty collections without specifying an `init` value, but in unambiguous cases the -identity value may be returned; see [`Base.reduce_empty`](@ref) for more details. +Apply function `f` to each element(s) in `itrs`, and then repeatedly call the 2 argument +function `op` with those results or results from previous `op` evaluations until a single value is returned. + +If provided, `init` is included exactly once as the left-most argument to `op` +for non-empty `itrs` and serves as the return value for empty `itrs`. It is +not transformed by the mapping function `f`. It is generally an error to call `mapreduce` +with empty collections without specifying an `init` value, but in unambiguous cases an +identity value for `op` may be returned; see [`Base.reduce_empty`](@ref) for more details. + +In contrast with [`mapfoldl`](@ref) and [`mapfoldr`](@ref), the sequence of +function evaluations and the associativity of the reduction is not specified +and may vary between different methods and Julia versions. +For example, `mapreduce(√, +, [1, 4, 9])` may be evaluated as either +`(√1+√4)+√9` (left-associative) _or_ `√1+(√4+√9)` (right-associative). +The return value for non-associative `op` functions may vary between +different methods and between Julia versions. For example, `-` is not +associative and thus `mapreduce(√, -, [1, 4, 9])` may return either +`-4.0` or `2.0` depending upon the exact method or version of Julia. +This is also true of some floating point operations that are typically +associative, for example `mapreduce(identity, +, [.1, .2, .3])` may return +either `0.6` or `0.6000000000000001`. + +While the associativity of the reduction is not defined, `mapreduce` does preserve +the ordering of the iterator for ordered collections. For example, +`mapreduce(uppercase, string, ['j','u','l','i','a'])` is guaranteed to always +return the properly-spelled `"JULIA"` because `Array`s are ordered collections; +the returned ordering is not guaranteed with an unordered collection like `Set`. [`mapreduce`](@ref) is functionally equivalent to calling `reduce(op, map(f, itrs...); init=init)`, but will in general execute faster since no @@ -309,8 +322,17 @@ should be used instead: [`maximum`](@ref)`(itr)`, [`minimum`](@ref)`(itr)`, [`su # Examples ```jldoctest -julia> mapreduce(x->x^2, +, [1:3;]) # == 1 + 4 + 9 -14 +julia> mapreduce(√, +, [1, 4, 9]) +6.0 + +julia> mapreduce(identity, +, [.1, .2, .3]) ≈ 0.6 +true + +julia> mapreduce(uppercase, string, ['j','u','l','i','a']) +"JULIA" + +julia> mapreduce(uppercase, string, ['j','u','l','i','a'], init="Hello ") +"Hello JULIA" ``` """ mapreduce(f, op, itr; kw...) = mapfoldl(f, op, itr; kw...) @@ -461,37 +483,49 @@ _mapreduce(f, op, ::IndexCartesian, A::AbstractArrayOrBroadcasted) = mapfoldl(f, """ reduce(op, itr; [init]) -Reduce the given collection `itr` with the given binary operator `op`. - -The order of evaluations and the associativity of the reduction is unspecified. -This means that you shouldn't -use non-associative operations like `-` because it is undefined whether `reduce(-,[1,2,3])` -will be evaluated as `(1-2)-3` or `1-(2-3)`. Use -[`foldl`](@ref) or [`foldr`](@ref) for guaranteed left or right associativity. - -If provided, `init` serves as the return value for empty `itrs`. For non-empty iterators, -it is included in the reduction exactly once and ensures that every element in `itrs` is -used as an argument to `op`. Like the reduction itself, the exact order and associativity -of how `init` is included is not specified. It is generally an error to call `mapreduce` -with empty collections without specifying an `init` value, but in unambiguous cases the -identity value may be returned; see [`Base.reduce_empty`](@ref) for more details. - -Reductions for certain commonly-used operators may have special implementations, and +Repeatedly call the 2 argument function `op` with the element(s) in `itr` +or results from previous `op` evaluations until a single value is returned. + +If provided, `init` is included exactly once as the left-most argument to `op` +for non-empty `itrs` and serves as the return value for empty `itrs`. It is generally an error to call `reduce` +with empty collections without specifying an `init` value, but in unambiguous cases an +identity value for `op` may be returned; see [`Base.reduce_empty`](@ref) for more details. + +In contrast with [`foldl`](@ref) and [`foldr`](@ref), the associativity of the reduction is not specified +and may vary between different methods and Julia versions. +For example, `reduce(+, [1, 2, 3])` may be evaluated as either +`(1+2)+3` (left-associative) _or_ `1+(2+3)` (right-associative). +The return value for non-associative `op` functions may vary between +different methods and between Julia versions. For example, `-` is not +associative and thus `reduce(-, [1, 2, 3])` may return either +`-4` or `2` depending upon the exact method or version of Julia. +This is also true of some floating point operations that are typically +associative, for example `reduce(+, [.1, .2, .3])` may return +either `0.6` or `0.6000000000000001`. + +While the associativity of the reduction is not defined, `reduce` does preserve +the ordering of the iterator for ordered collections. For example, +`reduce(string, ['J','u','l','i','a'])` is guaranteed to always +return the properly-spelled `"Julia"` because `Array`s are ordered collections; +the returned ordering is not guaranteed with an unordered collection like `Set`. + +Some commonly-used operators may have special implementations of a reduction, and should be used instead: [`maximum`](@ref)`(itr)`, [`minimum`](@ref)`(itr)`, [`sum`](@ref)`(itr)`, [`prod`](@ref)`(itr)`, [`any`](@ref)`(itr)`, [`all`](@ref)`(itr)`. -There are efficient methods for concatenating certain arrays of arrays -by calling `reduce(`[`vcat`](@ref)`, arr)` or `reduce(`[`hcat`](@ref)`, arr)`. - -Some operations accumulate error. Parallelism will be easier if the reduction can be -executed in groups. Future versions of Julia might change the algorithm. # Examples ```jldoctest -julia> reduce(*, [2; 3; 4]) -24 +julia> reduce(+, [1, 2, 3]) +6 + +julia> reduce(+, [.1, .2, .3]) ≈ 0.6 +true + +julia> reduce(string, ['J','u','l','i','a']) +"Julia" -julia> reduce(*, [2; 3; 4]; init=-1) --24 +julia> reduce(string, ['J','u','l','i','a'], init="Hello ") +"Hello Julia" ``` """ reduce(op, itr; kw...) = mapreduce(identity, op, itr; kw...) From 19b76d4b77dafbd2b1fd35c9ecf1808fbd21f4b0 Mon Sep 17 00:00:00 2001 From: Matt Bauman Date: Thu, 28 Mar 2024 14:30:50 -0400 Subject: [PATCH 04/18] Apply suggestions from code review Co-authored-by: Steven G. Johnson --- base/reduce.jl | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/base/reduce.jl b/base/reduce.jl index 3e997ad2b7fcc..f44280af14a48 100644 --- a/base/reduce.jl +++ b/base/reduce.jl @@ -298,15 +298,17 @@ The return value for non-associative `op` functions may vary between different methods and between Julia versions. For example, `-` is not associative and thus `mapreduce(√, -, [1, 4, 9])` may return either `-4.0` or `2.0` depending upon the exact method or version of Julia. -This is also true of some floating point operations that are typically -associative, for example `mapreduce(identity, +, [.1, .2, .3])` may return +Because floating-point roundoff errors typically break associativity, +even for operations like + that are associative in exact arithmetic, +this also means that the floating-point errors incurred by mapreduce +are implementation-defined; for example `mapreduce(identity, +, [.1, .2, .3])` may return either `0.6` or `0.6000000000000001`. While the associativity of the reduction is not defined, `mapreduce` does preserve -the ordering of the iterator for ordered collections. For example, -`mapreduce(uppercase, string, ['j','u','l','i','a'])` is guaranteed to always +the ordering of the iterator for ordered collections, so that the result does *not* require `op` to be commutative. For example, +`mapreduce(uppercase, *, ['j','u','l','i','a'])` is guaranteed to always return the properly-spelled `"JULIA"` because `Array`s are ordered collections; -the returned ordering is not guaranteed with an unordered collection like `Set`. +in contrast, the operand ordering is not guaranteed with an unordered collection like `Set`. [`mapreduce`](@ref) is functionally equivalent to calling `reduce(op, map(f, itrs...); init=init)`, but will in general execute faster since no @@ -314,7 +316,7 @@ intermediate collection needs to be created. See documentation for [`reduce`](@r [`map`](@ref). Some commonly-used operators may have special implementations of a mapped reduction, and -should be used instead: [`maximum`](@ref)`(itr)`, [`minimum`](@ref)`(itr)`, [`sum`](@ref)`(itr)`, +are recommended instead of `mapreduce`: [`maximum`](@ref)`(itr)`, [`minimum`](@ref)`(itr)`, [`sum`](@ref)`(itr)`, [`prod`](@ref)`(itr)`, [`any`](@ref)`(itr)`, [`all`](@ref)`(itr)`. !!! compat "Julia 1.2" @@ -328,10 +330,10 @@ julia> mapreduce(√, +, [1, 4, 9]) julia> mapreduce(identity, +, [.1, .2, .3]) ≈ 0.6 true -julia> mapreduce(uppercase, string, ['j','u','l','i','a']) +julia> mapreduce(uppercase, *, ['j','u','l','i','a']) "JULIA" -julia> mapreduce(uppercase, string, ['j','u','l','i','a'], init="Hello ") +julia> mapreduce(uppercase, *, ['j','u','l','i','a'], init="Hello ") "Hello JULIA" ``` """ From 793a4e0c73012c77c42131aec1fd27ca837fb0a7 Mon Sep 17 00:00:00 2001 From: Matt Bauman Date: Fri, 29 Mar 2024 11:50:25 -0400 Subject: [PATCH 05/18] Add NEWS.md --- NEWS.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/NEWS.md b/NEWS.md index 72b4629fe4174..14968586df914 100644 --- a/NEWS.md +++ b/NEWS.md @@ -77,6 +77,14 @@ New library features Standard library changes ------------------------ +* The `init` keyword for `reduce` and other reduction functions without guaranteed + associativity (`mapreduce`, `maximum`, `minimum`, `sum`, `prod`, `any`, and `all`) + now provides greater gaurantees on how its value is incorporated into the reduction: + it is used exactly once as the left-most argument for all non-empty collections, + and it is no longer required to be a "neutral" operand for the reduction. + Previously, its semantics for non-empty collections was explicitly not specified, allowing + implementations to use it 0, 1, or more times in the reduction ([#53871]). + #### StyledStrings #### JuliaSyntaxHighlighting From 7859c7ab9aa4ebf8725f902e8ce68f316718763c Mon Sep 17 00:00:00 2001 From: Matt Bauman Date: Fri, 29 Mar 2024 11:51:58 -0400 Subject: [PATCH 06/18] typo fix --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 14968586df914..5dbd8ee20c95d 100644 --- a/NEWS.md +++ b/NEWS.md @@ -79,7 +79,7 @@ Standard library changes * The `init` keyword for `reduce` and other reduction functions without guaranteed associativity (`mapreduce`, `maximum`, `minimum`, `sum`, `prod`, `any`, and `all`) - now provides greater gaurantees on how its value is incorporated into the reduction: + now provides greater guarantees on how its value is incorporated into the reduction: it is used exactly once as the left-most argument for all non-empty collections, and it is no longer required to be a "neutral" operand for the reduction. Previously, its semantics for non-empty collections was explicitly not specified, allowing From a5288b6db67a7e85ddf8cf0120512892121b9485 Mon Sep 17 00:00:00 2001 From: Matt Bauman Date: Wed, 3 Apr 2024 17:58:02 -0400 Subject: [PATCH 07/18] Revert back to the neutral element init and add more clarifications --- base/reduce.jl | 66 +++++++++++++++++++++++++++----------------------- 1 file changed, 36 insertions(+), 30 deletions(-) diff --git a/base/reduce.jl b/base/reduce.jl index f44280af14a48..69685d8cdd227 100644 --- a/base/reduce.jl +++ b/base/reduce.jl @@ -283,23 +283,29 @@ mapreduce_impl(f, op, A::AbstractArrayOrBroadcasted, ifirst::Integer, ilast::Int Apply function `f` to each element(s) in `itrs`, and then repeatedly call the 2 argument function `op` with those results or results from previous `op` evaluations until a single value is returned. -If provided, `init` is included exactly once as the left-most argument to `op` -for non-empty `itrs` and serves as the return value for empty `itrs`. It is -not transformed by the mapping function `f`. It is generally an error to call `mapreduce` +If provided, `init` provides the return value for empty collections and is used one or more times as +an argument to `op` for non-empty collections. The `init` value is not transformed by the function `f`. +Using `init` ensures that all calls to `op` take one argument from the (mapped) collection(s) and the other +from either `init` or the result from a previous `op` evaluation, and the ordering of these arguments is +unspecified. As it may appear in a reduction one or more times, it must be a neutral element for `op` that +does not change the result by being used more than once. It is generally an error to call `mapreduce` with empty collections without specifying an `init` value, but in unambiguous cases an identity value for `op` may be returned; see [`Base.reduce_empty`](@ref) for more details. In contrast with [`mapfoldl`](@ref) and [`mapfoldr`](@ref), the sequence of function evaluations and the associativity of the reduction is not specified -and may vary between different methods and Julia versions. -For example, `mapreduce(√, +, [1, 4, 9])` may be evaluated as either -`(√1+√4)+√9` (left-associative) _or_ `√1+(√4+√9)` (right-associative). -The return value for non-associative `op` functions may vary between -different methods and between Julia versions. For example, `-` is not -associative and thus `mapreduce(√, -, [1, 4, 9])` may return either -`-4.0` or `2.0` depending upon the exact method or version of Julia. +and may vary between different methods and across Julia versions. Some implementations +may reuse the return value of `f` for elements that appear multiple times in the +collection(s). + +For example, `mapreduce(√, +, [1, 4, 9, 16])` may be evaluated as the left-associative +`((√1+√4)+√9)+√16` _or_ the right-associative `√1+(√4+(√9+√16))` +_or_ as the potentially-parallel `(√1+√4)+(√9+√16)` and returns `10.0` regardless. +A non-associative function like `-` is not a valid `op` argument +as `mapreduce(√, -, [1, 4, 9, 16])` may return any of `-4.0`, `-2.0` or +`0.0`, depending upon which of the above associativity strategies is used. Because floating-point roundoff errors typically break associativity, -even for operations like + that are associative in exact arithmetic, +even for common operations like + that are associative in exact arithmetic, this also means that the floating-point errors incurred by mapreduce are implementation-defined; for example `mapreduce(identity, +, [.1, .2, .3])` may return either `0.6` or `0.6000000000000001`. @@ -332,9 +338,6 @@ true julia> mapreduce(uppercase, *, ['j','u','l','i','a']) "JULIA" - -julia> mapreduce(uppercase, *, ['j','u','l','i','a'], init="Hello ") -"Hello JULIA" ``` """ mapreduce(f, op, itr; kw...) = mapfoldl(f, op, itr; kw...) @@ -488,21 +491,27 @@ _mapreduce(f, op, ::IndexCartesian, A::AbstractArrayOrBroadcasted) = mapfoldl(f, Repeatedly call the 2 argument function `op` with the element(s) in `itr` or results from previous `op` evaluations until a single value is returned. -If provided, `init` is included exactly once as the left-most argument to `op` -for non-empty `itrs` and serves as the return value for empty `itrs`. It is generally an error to call `reduce` -with empty collections without specifying an `init` value, but in unambiguous cases an +If provided, `init` provides the return value for empty collections and is used one or more times as +an argument to `op` for non-empty collections. +Using `init` ensures that all calls to `op` take one argument from `itr` and the other +from either `init` or the result from a previous `op` evaluation, and the ordering of these arguments is +unspecified. As it may appear in a reduction one or more times, it must be a neutral element for `op` that +does not change the result by being used more than once. It is generally an error to call `reduce` +with an empty collection without specifying an `init` value, but in unambiguous cases an identity value for `op` may be returned; see [`Base.reduce_empty`](@ref) for more details. -In contrast with [`foldl`](@ref) and [`foldr`](@ref), the associativity of the reduction is not specified -and may vary between different methods and Julia versions. -For example, `reduce(+, [1, 2, 3])` may be evaluated as either -`(1+2)+3` (left-associative) _or_ `1+(2+3)` (right-associative). -The return value for non-associative `op` functions may vary between -different methods and between Julia versions. For example, `-` is not -associative and thus `reduce(-, [1, 2, 3])` may return either -`-4` or `2` depending upon the exact method or version of Julia. -This is also true of some floating point operations that are typically -associative, for example `reduce(+, [.1, .2, .3])` may return +In contrast with [`foldl`](@ref) and [`foldr`](@ref), the associativity of the reduction is +not specified and may vary between different methods and across Julia versions. +For example, `reduce(+, [1, 2, 3, 4])` may be evaluated as the left-associative +`((1+2)+3)+4` _or_ the right-associative `1+(2+(3+4))` +_or_ as the potentially-parallel `(1+2)+(3+4)` and returns `10.0` regardless. +A non-associative function like `-` is not a valid `op` argument +as `reduce(-, [1, 2, 3, 4])` may return any of `-4.0`, `-2.0` or +`0.0`, depending upon which of the above associativity strategies is used. +Because floating-point roundoff errors typically break associativity, +even for common operations like + that are associative in exact arithmetic, +this also means that the floating-point errors incurred by reduce +are implementation-defined; for example `reduce(+, [.1, .2, .3])` may return either `0.6` or `0.6000000000000001`. While the associativity of the reduction is not defined, `reduce` does preserve @@ -525,9 +534,6 @@ true julia> reduce(string, ['J','u','l','i','a']) "Julia" - -julia> reduce(string, ['J','u','l','i','a'], init="Hello ") -"Hello Julia" ``` """ reduce(op, itr; kw...) = mapreduce(identity, op, itr; kw...) From 0b09dcd4f9ae17aa1473cc92ec600d08b1f4221b Mon Sep 17 00:00:00 2001 From: Matt Bauman Date: Wed, 3 Apr 2024 18:05:55 -0400 Subject: [PATCH 08/18] update NEWS --- NEWS.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/NEWS.md b/NEWS.md index 5dbd8ee20c95d..2072ec7bf0e17 100644 --- a/NEWS.md +++ b/NEWS.md @@ -77,13 +77,11 @@ New library features Standard library changes ------------------------ -* The `init` keyword for `reduce` and other reduction functions without guaranteed +* The `init` keyword argument in `reduce(op, itr; init)` and other reduction functions with implementation-defined associativity (`mapreduce`, `maximum`, `minimum`, `sum`, `prod`, `any`, and `all`) - now provides greater guarantees on how its value is incorporated into the reduction: - it is used exactly once as the left-most argument for all non-empty collections, - and it is no longer required to be a "neutral" operand for the reduction. - Previously, its semantics for non-empty collections was explicitly not specified, allowing - implementations to use it 0, 1, or more times in the reduction ([#53871]). + is now guaranteed to be used for non-empty collections one or more times. This ensures that all calls + to the reducing function `op` take one argument from `itr` and the other from either `init` or the result from a + previous `op` evaluation. Previously it was explicitly allowed to be omitted from the reduction entirely. #### StyledStrings From d2a1fbba9b3a29ec16b920643292ebaebe794b40 Mon Sep 17 00:00:00 2001 From: Matt Bauman Date: Wed, 3 Apr 2024 18:15:35 -0400 Subject: [PATCH 09/18] minor wording --- base/reduce.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/reduce.jl b/base/reduce.jl index 69685d8cdd227..b231191af6ab1 100644 --- a/base/reduce.jl +++ b/base/reduce.jl @@ -283,7 +283,7 @@ mapreduce_impl(f, op, A::AbstractArrayOrBroadcasted, ifirst::Integer, ilast::Int Apply function `f` to each element(s) in `itrs`, and then repeatedly call the 2 argument function `op` with those results or results from previous `op` evaluations until a single value is returned. -If provided, `init` provides the return value for empty collections and is used one or more times as +If provided, `init` is the return value for empty collections and is used one or more times as an argument to `op` for non-empty collections. The `init` value is not transformed by the function `f`. Using `init` ensures that all calls to `op` take one argument from the (mapped) collection(s) and the other from either `init` or the result from a previous `op` evaluation, and the ordering of these arguments is From a1963f31d112a1987345a63bfa3e27c89f7996ec Mon Sep 17 00:00:00 2001 From: Matt Bauman Date: Wed, 3 Apr 2024 18:24:08 -0400 Subject: [PATCH 10/18] more minor wording --- base/reduce.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/reduce.jl b/base/reduce.jl index b231191af6ab1..490e3ff6630e7 100644 --- a/base/reduce.jl +++ b/base/reduce.jl @@ -287,7 +287,7 @@ If provided, `init` is the return value for empty collections and is used one or an argument to `op` for non-empty collections. The `init` value is not transformed by the function `f`. Using `init` ensures that all calls to `op` take one argument from the (mapped) collection(s) and the other from either `init` or the result from a previous `op` evaluation, and the ordering of these arguments is -unspecified. As it may appear in a reduction one or more times, it must be a neutral element for `op` that +unspecified. As it may appear in the reduction one or more times, it must be a neutral element for `op` that does not change the result by being used more than once. It is generally an error to call `mapreduce` with empty collections without specifying an `init` value, but in unambiguous cases an identity value for `op` may be returned; see [`Base.reduce_empty`](@ref) for more details. From 653f33b6a57ae665c185fb217bb570dae9d04e97 Mon Sep 17 00:00:00 2001 From: Matt Bauman Date: Wed, 3 Apr 2024 18:31:13 -0400 Subject: [PATCH 11/18] Propagate changes to other reduce family members --- base/reduce.jl | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/base/reduce.jl b/base/reduce.jl index 490e3ff6630e7..a9170f0355132 100644 --- a/base/reduce.jl +++ b/base/reduce.jl @@ -554,7 +554,7 @@ The return type is `Int` for signed integers of less than system word size, and arguments, a common return type is found to which all arguments are promoted. The value returned for empty `itr` can be specified by `init`. It must be -the additive identity (i.e. zero) as it is unspecified whether `init` is used +the additive identity (i.e. zero) as it may be used one or more times for non-empty collections. !!! compat "Julia 1.6" @@ -593,7 +593,7 @@ The return type is `Int` for signed integers of less than system word size, and arguments, a common return type is found to which all arguments are promoted. The value returned for empty `itr` can be specified by `init`. It must be -the additive identity (i.e. zero) as it is unspecified whether `init` is used +the additive identity (i.e. zero) as it may be used one or more times for non-empty collections. !!! compat "Julia 1.6" @@ -625,7 +625,7 @@ The return type is `Int` for signed integers of less than system word size, and arguments, a common return type is found to which all arguments are promoted. The value returned for empty `itr` can be specified by `init`. It must be the -multiplicative identity (i.e. one) as it is unspecified whether `init` is used +multiplicative identity (i.e. one) as it may be used one or more times for non-empty collections. !!! compat "Julia 1.6" @@ -649,7 +649,7 @@ The return type is `Int` for signed integers of less than system word size, and arguments, a common return type is found to which all arguments are promoted. The value returned for empty `itr` can be specified by `init`. It must be the -multiplicative identity (i.e. one) as it is unspecified whether `init` is used +multiplicative identity (i.e. one) as it may be used one or more times for non-empty collections. !!! compat "Julia 1.6" @@ -733,7 +733,7 @@ Return the largest result of calling function `f` on each element of `itr`. The value returned for empty `itr` can be specified by `init`. It must be a neutral element for `max` (i.e. which is less than or equal to any -other element) as it is unspecified whether `init` is used +other element) as it may be used one or more times for non-empty collections. !!! compat "Julia 1.6" @@ -760,7 +760,7 @@ Return the smallest result of calling function `f` on each element of `itr`. The value returned for empty `itr` can be specified by `init`. It must be a neutral element for `min` (i.e. which is greater than or equal to any -other element) as it is unspecified whether `init` is used +other element) as it may be used one or more times for non-empty collections. !!! compat "Julia 1.6" @@ -787,7 +787,7 @@ Return the largest element in a collection. The value returned for empty `itr` can be specified by `init`. It must be a neutral element for `max` (i.e. which is less than or equal to any -other element) as it is unspecified whether `init` is used +other element) as it may be used one or more times for non-empty collections. !!! compat "Julia 1.6" @@ -819,7 +819,7 @@ Return the smallest element in a collection. The value returned for empty `itr` can be specified by `init`. It must be a neutral element for `min` (i.e. which is greater than or equal to any -other element) as it is unspecified whether `init` is used +other element) as it may be used one or more times for non-empty collections. !!! compat "Julia 1.6" @@ -854,7 +854,7 @@ The value returned for empty `itr` can be specified by `init`. It must be a 2-tu first and second elements are neutral elements for `min` and `max` respectively (i.e. which are greater/less than or equal to any other element). As a consequence, when `itr` is empty the returned `(mn, mx)` tuple will satisfy `mn ≥ mx`. When `init` is -specified it may be used even for non-empty `itr`. +specified it may be used one or more times for non-empty `itr`. !!! compat "Julia 1.8" Keyword argument `init` requires Julia 1.8 or later. @@ -881,7 +881,7 @@ return them as a 2-tuple. Only one pass is made over `itr`. The value returned for empty `itr` can be specified by `init`. It must be a 2-tuple whose first and second elements are neutral elements for `min` and `max` respectively -(i.e. which are greater/less than or equal to any other element). It is used for non-empty +(i.e. which are greater/less than or equal to any other element). It is used one or more times for non-empty collections. Note: it implies that, for empty `itr`, the returned value `(mn, mx)` satisfies `mn ≥ mx` even though for non-empty `itr` it satisfies `mn ≤ mx`. This is a "paradoxical" but yet expected result. From c1d9110ac926e62d3814c7c3eda44eadef245830 Mon Sep 17 00:00:00 2001 From: Matt Bauman Date: Wed, 3 Apr 2024 18:45:52 -0400 Subject: [PATCH 12/18] whitespace --- base/reduce.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/base/reduce.jl b/base/reduce.jl index a9170f0355132..29f831e8ed161 100644 --- a/base/reduce.jl +++ b/base/reduce.jl @@ -287,7 +287,7 @@ If provided, `init` is the return value for empty collections and is used one or an argument to `op` for non-empty collections. The `init` value is not transformed by the function `f`. Using `init` ensures that all calls to `op` take one argument from the (mapped) collection(s) and the other from either `init` or the result from a previous `op` evaluation, and the ordering of these arguments is -unspecified. As it may appear in the reduction one or more times, it must be a neutral element for `op` that +unspecified. As it may appear in the reduction one or more times, it must be a neutral element for `op` that does not change the result by being used more than once. It is generally an error to call `mapreduce` with empty collections without specifying an `init` value, but in unambiguous cases an identity value for `op` may be returned; see [`Base.reduce_empty`](@ref) for more details. @@ -495,7 +495,7 @@ If provided, `init` provides the return value for empty collections and is used an argument to `op` for non-empty collections. Using `init` ensures that all calls to `op` take one argument from `itr` and the other from either `init` or the result from a previous `op` evaluation, and the ordering of these arguments is -unspecified. As it may appear in a reduction one or more times, it must be a neutral element for `op` that +unspecified. As it may appear in a reduction one or more times, it must be a neutral element for `op` that does not change the result by being used more than once. It is generally an error to call `reduce` with an empty collection without specifying an `init` value, but in unambiguous cases an identity value for `op` may be returned; see [`Base.reduce_empty`](@ref) for more details. From 05c9e7a668446b95430d212bcc1e1abeef56a01c Mon Sep 17 00:00:00 2001 From: Matt Bauman Date: Wed, 3 Apr 2024 19:00:06 -0400 Subject: [PATCH 13/18] fixup wordings around the arguments to `op` --- NEWS.md | 4 ++-- base/reduce.jl | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/NEWS.md b/NEWS.md index 2072ec7bf0e17..15cfcafc6727b 100644 --- a/NEWS.md +++ b/NEWS.md @@ -80,8 +80,8 @@ Standard library changes * The `init` keyword argument in `reduce(op, itr; init)` and other reduction functions with implementation-defined associativity (`mapreduce`, `maximum`, `minimum`, `sum`, `prod`, `any`, and `all`) is now guaranteed to be used for non-empty collections one or more times. This ensures that all calls - to the reducing function `op` take one argument from `itr` and the other from either `init` or the result from a - previous `op` evaluation. Previously it was explicitly allowed to be omitted from the reduction entirely. + to the reducing function `op` have one argument that is either `init` or the result from a + previous `op` evaluation. Previously `init` was explicitly allowed to be omitted from the reduction entirely. #### StyledStrings diff --git a/base/reduce.jl b/base/reduce.jl index 29f831e8ed161..31dc15ed13567 100644 --- a/base/reduce.jl +++ b/base/reduce.jl @@ -285,8 +285,8 @@ function `op` with those results or results from previous `op` evaluations until If provided, `init` is the return value for empty collections and is used one or more times as an argument to `op` for non-empty collections. The `init` value is not transformed by the function `f`. -Using `init` ensures that all calls to `op` take one argument from the (mapped) collection(s) and the other -from either `init` or the result from a previous `op` evaluation, and the ordering of these arguments is +Using `init` ensures that all calls to `op` have one argument that is either `init` +or the result from a previous `op` evaluation, and the ordering of these arguments is unspecified. As it may appear in the reduction one or more times, it must be a neutral element for `op` that does not change the result by being used more than once. It is generally an error to call `mapreduce` with empty collections without specifying an `init` value, but in unambiguous cases an @@ -493,8 +493,8 @@ or results from previous `op` evaluations until a single value is returned. If provided, `init` provides the return value for empty collections and is used one or more times as an argument to `op` for non-empty collections. -Using `init` ensures that all calls to `op` take one argument from `itr` and the other -from either `init` or the result from a previous `op` evaluation, and the ordering of these arguments is +Using `init` ensures that all calls to `op` have one argument that is either `init` +or the result from a previous `op` evaluation, and the ordering of these arguments is unspecified. As it may appear in a reduction one or more times, it must be a neutral element for `op` that does not change the result by being used more than once. It is generally an error to call `reduce` with an empty collection without specifying an `init` value, but in unambiguous cases an From 28669d35336b214fe54c92ff54c875a8a0121dad Mon Sep 17 00:00:00 2001 From: Matt Bauman Date: Wed, 17 Apr 2024 13:33:26 -0400 Subject: [PATCH 14/18] use extended help for (map)?reduce, propagate language to sum et al --- base/reduce.jl | 329 ++++++++++++++++++++++++++++++------------------- 1 file changed, 199 insertions(+), 130 deletions(-) diff --git a/base/reduce.jl b/base/reduce.jl index 31dc15ed13567..baba93e626bf3 100644 --- a/base/reduce.jl +++ b/base/reduce.jl @@ -283,62 +283,80 @@ mapreduce_impl(f, op, A::AbstractArrayOrBroadcasted, ifirst::Integer, ilast::Int Apply function `f` to each element(s) in `itrs`, and then repeatedly call the 2 argument function `op` with those results or results from previous `op` evaluations until a single value is returned. -If provided, `init` is the return value for empty collections and is used one or more times as -an argument to `op` for non-empty collections. The `init` value is not transformed by the function `f`. -Using `init` ensures that all calls to `op` have one argument that is either `init` -or the result from a previous `op` evaluation, and the ordering of these arguments is -unspecified. As it may appear in the reduction one or more times, it must be a neutral element for `op` that -does not change the result by being used more than once. It is generally an error to call `mapreduce` -with empty collections without specifying an `init` value, but in unambiguous cases an -identity value for `op` may be returned; see [`Base.reduce_empty`](@ref) for more details. - -In contrast with [`mapfoldl`](@ref) and [`mapfoldr`](@ref), the sequence of -function evaluations and the associativity of the reduction is not specified -and may vary between different methods and across Julia versions. Some implementations +The optional `init` keyword argument must be an identity element for `op`. +The `init` value is not transformed by the function `f`. +When `init` is provided, all initial evaluation(s) of `op` use `init` and an +element from `itr` as its arguments with an unspecified argument ordering. +For empty collections, `init` is the return value. It is generally an error to call `mapreduce` +with an empty collection without specifying an `init` value, but in unambiguous cases a known +identity element for `op` may be returned; see [`Base.mapreduce_empty`](@ref) for more details. + +The reduction function `op` must be an associative operation. In contrast with +[`mapfoldl`](@ref) and [`mapfoldr`](@ref), the sequence of +function evaluations and the associativity of `mapreduce` is +not specified and may vary between different methods and across Julia versions. Some implementations may reuse the return value of `f` for elements that appear multiple times in the -collection(s). - -For example, `mapreduce(√, +, [1, 4, 9, 16])` may be evaluated as the left-associative -`((√1+√4)+√9)+√16` _or_ the right-associative `√1+(√4+(√9+√16))` -_or_ as the potentially-parallel `(√1+√4)+(√9+√16)` and returns `10.0` regardless. -A non-associative function like `-` is not a valid `op` argument -as `mapreduce(√, -, [1, 4, 9, 16])` may return any of `-4.0`, `-2.0` or -`0.0`, depending upon which of the above associativity strategies is used. -Because floating-point roundoff errors typically break associativity, -even for common operations like + that are associative in exact arithmetic, -this also means that the floating-point errors incurred by mapreduce -are implementation-defined; for example `mapreduce(identity, +, [.1, .2, .3])` may return -either `0.6` or `0.6000000000000001`. - -While the associativity of the reduction is not defined, `mapreduce` does preserve -the ordering of the iterator for ordered collections, so that the result does *not* require `op` to be commutative. For example, -`mapreduce(uppercase, *, ['j','u','l','i','a'])` is guaranteed to always -return the properly-spelled `"JULIA"` because `Array`s are ordered collections; -in contrast, the operand ordering is not guaranteed with an unordered collection like `Set`. - -[`mapreduce`](@ref) is functionally equivalent to calling -`reduce(op, map(f, itrs...); init=init)`, but will in general execute faster since no -intermediate collection needs to be created. See documentation for [`reduce`](@ref) and -[`map`](@ref). +collection(s). When `itr` is an ordered collection, `mapreduce` preserves the ordering of the +iterator in the reduction and so `op` is not required to be commutative. Some commonly-used operators may have special implementations of a mapped reduction, and -are recommended instead of `mapreduce`: [`maximum`](@ref)`(itr)`, [`minimum`](@ref)`(itr)`, [`sum`](@ref)`(itr)`, +should be used instead: [`maximum`](@ref)`(itr)`, [`minimum`](@ref)`(itr)`, [`sum`](@ref)`(itr)`, [`prod`](@ref)`(itr)`, [`any`](@ref)`(itr)`, [`all`](@ref)`(itr)`. -!!! compat "Julia 1.2" - `mapreduce` with multiple iterators requires Julia 1.2 or later. - # Examples ```jldoctest -julia> mapreduce(√, +, [1, 4, 9]) -6.0 +julia> mapreduce(√, +, [1, 4, 9, 16]) +10.0 -julia> mapreduce(identity, +, [.1, .2, .3]) ≈ 0.6 +julia> mapreduce(x->x/10, +, [1, 2, 3, 4]) ≈ 1.0 true julia> mapreduce(uppercase, *, ['j','u','l','i','a']) "JULIA" ``` + +# Extended Help + +## Arbitrary associativity: examples and consequences + +The associativity of the reduction is not specified, so the `op` function +must be associative and `init` (if provided) must be an identity element. +To demonstrate this, consider the example: + +```jldoctest +julia> mapreduce(√, +, [1, 4, 9, 16]; init=0) +10 +``` + +There are many possible ways in which `reduce` might compute this, +including the left-associative `(((0+√1)+√4)+√9)+√16` (like [`foldl`](@ref)) +or the right-associative `√1+(√4+(√9+(√16+0)))` (like [`foldr`](@ref)) +or as a potentially-parallel `((0+√1)+(0+√4))+((0+√9)+(0+√16))`. The exact +strategy does not matter; `reduce` returns `10.0` because `+` is associative +and `0` is a commutative identity element for `+`. Note how the `init` +value may be used one or more times with possibly varying argument orderings and +is not transformed by `√`. + +In contrast, the `-` function is not associative and is not a valid `op` for `reduce`. +Were `-` erroneously used instead of `+` in the above example, the three example +strategies return three different results (`-10.0`, `-2.0` and `0.0`, respectively). + +More subtly, floating point arithmetic is _also_ typically non-associative, +even for common operations like `+` that are associative in exact arithmetic. +This means that the magnitude of floating-point errors incurred by `mapreduce` are +also unspecified. For example, `mapreduce(x->x/10, +, [1, 2, 3, 4])` may return +either `1.0` or `0.9999999999999999`, and both are [`≈`](@ref) to `1.0`: + +```jldoctest +julia> mapfoldl(x->x/10, +, [1, 2, 3, 4]) +1.0 + +julia> mapfoldr(x->x/10, +, [1, 2, 3, 4]) +0.9999999999999999 + +julia> mapreduce(x->x/10, +, [1, 2, 3, 4]) ≈ 1.0 +true +``` """ mapreduce(f, op, itr; kw...) = mapfoldl(f, op, itr; kw...) mapreduce(f, op, itrs...; kw...) = reduce(op, Generator(f, itrs...); kw...) @@ -491,34 +509,18 @@ _mapreduce(f, op, ::IndexCartesian, A::AbstractArrayOrBroadcasted) = mapfoldl(f, Repeatedly call the 2 argument function `op` with the element(s) in `itr` or results from previous `op` evaluations until a single value is returned. -If provided, `init` provides the return value for empty collections and is used one or more times as -an argument to `op` for non-empty collections. -Using `init` ensures that all calls to `op` have one argument that is either `init` -or the result from a previous `op` evaluation, and the ordering of these arguments is -unspecified. As it may appear in a reduction one or more times, it must be a neutral element for `op` that -does not change the result by being used more than once. It is generally an error to call `reduce` -with an empty collection without specifying an `init` value, but in unambiguous cases an -identity value for `op` may be returned; see [`Base.reduce_empty`](@ref) for more details. +The optional `init` keyword argument must be an identity element for `op`. +When `init` is provided, all initial evaluation(s) of `op` use `init` and an +element from `itr` as its arguments with an unspecified argument ordering. +For empty collections, `init` is the return value. It is generally an error to call `reduce` +with an empty collection without specifying an `init` value, but in unambiguous cases a known +identity element for `op` may be returned; see [`Base.reduce_empty`](@ref) for more details. -In contrast with [`foldl`](@ref) and [`foldr`](@ref), the associativity of the reduction is +The reduction function `op` must be an associative operation. In contrast with +[`foldl`](@ref) and [`foldr`](@ref), the associativity of `reduce` is not specified and may vary between different methods and across Julia versions. -For example, `reduce(+, [1, 2, 3, 4])` may be evaluated as the left-associative -`((1+2)+3)+4` _or_ the right-associative `1+(2+(3+4))` -_or_ as the potentially-parallel `(1+2)+(3+4)` and returns `10.0` regardless. -A non-associative function like `-` is not a valid `op` argument -as `reduce(-, [1, 2, 3, 4])` may return any of `-4.0`, `-2.0` or -`0.0`, depending upon which of the above associativity strategies is used. -Because floating-point roundoff errors typically break associativity, -even for common operations like + that are associative in exact arithmetic, -this also means that the floating-point errors incurred by reduce -are implementation-defined; for example `reduce(+, [.1, .2, .3])` may return -either `0.6` or `0.6000000000000001`. - -While the associativity of the reduction is not defined, `reduce` does preserve -the ordering of the iterator for ordered collections. For example, -`reduce(string, ['J','u','l','i','a'])` is guaranteed to always -return the properly-spelled `"Julia"` because `Array`s are ordered collections; -the returned ordering is not guaranteed with an unordered collection like `Set`. +When `itr` is an ordered collection, `reduce` preserves the ordering of the +iterator in the reduction and so `op` is not required to be commutative. Some commonly-used operators may have special implementations of a reduction, and should be used instead: [`maximum`](@ref)`(itr)`, [`minimum`](@ref)`(itr)`, [`sum`](@ref)`(itr)`, @@ -526,15 +528,57 @@ should be used instead: [`maximum`](@ref)`(itr)`, [`minimum`](@ref)`(itr)`, [`su # Examples ```jldoctest -julia> reduce(+, [1, 2, 3]) -6 +julia> reduce(+, [1, 2, 3, 4]; init=0) +10 -julia> reduce(+, [.1, .2, .3]) ≈ 0.6 +julia> reduce(+, [.1, .2, .3, .4]) ≈ 1.0 true -julia> reduce(string, ['J','u','l','i','a']) +julia> reduce(*, ['J','u','l','i','a']) "Julia" ``` + +# Extended Help + +## Arbitrary associativity: examples and consequences + +The associativity of the reduction is not specified, so the `op` function +must be associative and `init` (if provided) must be an identity element. +To demonstrate this, consider the example: + +```jldoctest +julia> reduce(+, [1, 2, 3, 4]; init=0) +10 +``` + +There are many possible ways in which `reduce` might compute this, +including the left-associative `(((0+1)+2)+3)+4` (like [`foldl`](@ref)) +or the right-associative `1+(2+(3+(4+0)))` (like [`foldr`](@ref)) +or as a potentially-parallel `((0+1)+(0+2))+((0+3)+(0+4))`. The exact +strategy does not matter; `reduce` returns `10` because `+` is associative +and `0` is a commutative identity element for `+`. Note how the `init` +value may be used one or more times with possibly varying argument orderings. + +In contrast, the `-` function is not associative and is not a valid `op` for `reduce`. +Were `-` erroneously used instead of `+` in the above example, the three example +strategies return three different results (`-10`, `-2` and `0`, respectively). + +More subtly, floating point arithmetic is _also_ typically non-associative, +even for common operations like `+` that are associative in exact arithmetic. +This means that the magnitude of floating-point errors incurred by `reduce` are +also unspecified. For example, `reduce(+, [.1, .2, .3, .4])` may return +either `1.0` or `0.9999999999999999`, and both are [`≈`](@ref) to `1.0`: + +```jldoctest +julia> foldl(+, [.1, .2, .3, .4]) +1.0 + +julia> foldr(+, [.1, .2, .3, .4]) +0.9999999999999999 + +julia> reduce(+, [.1, .2, .3, .4]) ≈ 1.0 +true +``` """ reduce(op, itr; kw...) = mapreduce(identity, op, itr; kw...) @@ -553,9 +597,13 @@ The return type is `Int` for signed integers of less than system word size, and `UInt` for unsigned integers of less than system word size. For all other arguments, a common return type is found to which all arguments are promoted. -The value returned for empty `itr` can be specified by `init`. It must be -the additive identity (i.e. zero) as it may be used one or more times -for non-empty collections. +The optional `init` keyword argument must be an additive identity (i.e., zero), +and it provides the return value for empty collections. It is generally an error to call `sum` +with an empty collection without specifying an `init` value, but in unambiguous cases the +[`zero`](@ref) of the return type may be returned. + +Like [`mapreduce`](@ref), the associativity of the additions is not specified and the return +value of `f` may be reused for repeated elements. !!! compat "Julia 1.6" Keyword argument `init` requires Julia 1.6 or later. @@ -565,21 +613,6 @@ for non-empty collections. julia> sum(abs2, [2; 3; 4]) 29 ``` - -Note the important difference between `sum(A)` and `reduce(+, A)` for arrays -with small integer eltype: - -```jldoctest -julia> sum(Int8[100, 28]) -128 - -julia> reduce(+, Int8[100, 28]) --128 -``` - -In the former case, the integers are widened to system word size and therefore -the result is 128. In the latter case, no such widening happens and integer -overflow results in -128. """ sum(f, a; kw...) = mapreduce(f, add_sum, a; kw...) @@ -592,9 +625,12 @@ The return type is `Int` for signed integers of less than system word size, and `UInt` for unsigned integers of less than system word size. For all other arguments, a common return type is found to which all arguments are promoted. -The value returned for empty `itr` can be specified by `init`. It must be -the additive identity (i.e. zero) as it may be used one or more times -for non-empty collections. +The optional `init` keyword argument must be an additive identity (i.e., zero), +and it provides the return value for empty collections. It is generally an error to call `sum` +with an empty collection without specifying an `init` value, but in unambiguous cases the +[`zero`](@ref) of the return type may be returned. + +Like [`reduce`](@ref), the associativity of the additions is not specified. !!! compat "Julia 1.6" Keyword argument `init` requires Julia 1.6 or later. @@ -609,6 +645,21 @@ julia> sum(1:20) julia> sum(1:20; init = 0.0) 210.0 ``` + +Note the important difference between `sum(A)` and `reduce(+, A)` for arrays +with small integer eltype: + +```jldoctest +julia> sum(Int8[100, 28]) +128 + +julia> reduce(+, Int8[100, 28]) +-128 +``` + +In the former case, the integers are widened to system word size and therefore +the result is 128. In the latter case, no such widening happens and integer +overflow results in -128. """ sum(a; kw...) = sum(identity, a; kw...) sum(a::AbstractArray{Bool}; kw...) = @@ -624,9 +675,13 @@ The return type is `Int` for signed integers of less than system word size, and `UInt` for unsigned integers of less than system word size. For all other arguments, a common return type is found to which all arguments are promoted. -The value returned for empty `itr` can be specified by `init`. It must be the -multiplicative identity (i.e. one) as it may be used one or more times -for non-empty collections. +The optional `init` keyword argument must be a multiplicative identity (i.e., one), +and it provides the return value for empty collections. It is generally an error to call `prod` +with an empty collection without specifying an `init` value, but in unambiguous cases the +[`one`](@ref) of the return type may be returned. + +Like [`mapreduce`](@ref), the associativity of the multiplications is not specified and the return +value of `f` may be reused for repeated elements. !!! compat "Julia 1.6" Keyword argument `init` requires Julia 1.6 or later. @@ -648,9 +703,12 @@ The return type is `Int` for signed integers of less than system word size, and `UInt` for unsigned integers of less than system word size. For all other arguments, a common return type is found to which all arguments are promoted. -The value returned for empty `itr` can be specified by `init`. It must be the -multiplicative identity (i.e. one) as it may be used one or more times -for non-empty collections. +The optional `init` keyword argument must be a multiplicative identity (i.e., one), +and it provides the return value for empty collections. It is generally an error to call `prod` +with an empty collection without specifying an `init` value, but in unambiguous cases the +[`one`](@ref) of the return type may be returned. + +Like [`reduce`](@ref), the associativity of the multiplications is not specified. !!! compat "Julia 1.6" Keyword argument `init` requires Julia 1.6 or later. @@ -731,10 +789,13 @@ end Return the largest result of calling function `f` on each element of `itr`. -The value returned for empty `itr` can be specified by `init`. It must be -a neutral element for `max` (i.e. which is less than or equal to any -other element) as it may be used one or more times -for non-empty collections. +The optional `init` keyword argument must be an identity for `max` (i.e., less than or +equal to all values), and it provides the return value for empty collections. It is +an error to call `maximum` with an empty collection without specifying an `init` value. + +Like [`mapreduce`](@ref), the associativity of the `max` operations is not specified and +the return value of `f` may be reused for repeated elements. +Use [`mapfoldl(f, max, itr)`](@ref) for stronger guarantees on the evaluation strategy. !!! compat "Julia 1.6" Keyword argument `init` requires Julia 1.6 or later. @@ -758,10 +819,13 @@ maximum(f, a; kw...) = mapreduce(f, max, a; kw...) Return the smallest result of calling function `f` on each element of `itr`. -The value returned for empty `itr` can be specified by `init`. It must be -a neutral element for `min` (i.e. which is greater than or equal to any -other element) as it may be used one or more times -for non-empty collections. +The optional `init` keyword argument must be an identity for `min` (i.e., less than or +equal to all values), and it provides the return value for empty collections. It is +an error to call `minimum` with an empty collection without specifying an `init` value. + +Like [`mapreduce`](@ref), the associativity of the `min` operations is not specified and +the return value of `f` may be reused for repeated elements. +Use [`mapfoldl(f, min, itr)`](@ref) for stronger guarantees on the evaluation strategy. !!! compat "Julia 1.6" Keyword argument `init` requires Julia 1.6 or later. @@ -785,10 +849,12 @@ minimum(f, a; kw...) = mapreduce(f, min, a; kw...) Return the largest element in a collection. -The value returned for empty `itr` can be specified by `init`. It must be -a neutral element for `max` (i.e. which is less than or equal to any -other element) as it may be used one or more times -for non-empty collections. +The optional `init` keyword argument must be an identity for `max` (i.e., less than or +equal to all values), and it provides the return value for empty collections. It is +an error to call `maximum` with an empty collection without specifying an `init` value. + +Like [`reduce`](@ref), the associativity of the `max` operations is not specified. +Use [`foldl(max, itr)`](@ref) for stronger guarantees on the evaluation strategy. !!! compat "Julia 1.6" Keyword argument `init` requires Julia 1.6 or later. @@ -817,10 +883,12 @@ maximum(a; kw...) = mapreduce(identity, max, a; kw...) Return the smallest element in a collection. -The value returned for empty `itr` can be specified by `init`. It must be -a neutral element for `min` (i.e. which is greater than or equal to any -other element) as it may be used one or more times -for non-empty collections. +The optional `init` keyword argument must be an identity for `min` (i.e., less than or +equal to all values), and it provides the return value for empty collections. It is +an error to call `minimum` with an empty collection without specifying an `init` value. + +Like [`reduce`](@ref), the associativity of the `min` operations is not specified. +Use [`foldl(min, itr)`](@ref) for stronger guarantees on the evaluation strategy. !!! compat "Julia 1.6" Keyword argument `init` requires Julia 1.6 or later. @@ -847,14 +915,15 @@ minimum(a; kw...) = mapreduce(identity, min, a; kw...) """ extrema(itr; [init]) -> (mn, mx) -Compute both the minimum `mn` and maximum `mx` element in a single pass, and return them +Compute both the [`minimum`](@ref) `mn` and [`maximum`](@ref) `mx` element in an efficient manner, and return them as a 2-tuple. -The value returned for empty `itr` can be specified by `init`. It must be a 2-tuple whose -first and second elements are neutral elements for `min` and `max` respectively -(i.e. which are greater/less than or equal to any other element). As a consequence, when -`itr` is empty the returned `(mn, mx)` tuple will satisfy `mn ≥ mx`. When `init` is -specified it may be used one or more times for non-empty `itr`. +The optional `init` keyword argument must be a 2-tuple whose elements are identities +for `min` and `max`, respectively (i.e. which are greater/less than or equal to any +other element), and it provides the return value for empty collections. As a consequence, +the initial minimum must be greater than or equal to the initial maximum, and when +`itr` is empty the returned `(mn, mx)` tuple will have `mn ≥ mx`. It is +an error to call `extrema` with an empty collection without specifying an `init` value. !!! compat "Julia 1.8" Keyword argument `init` requires Julia 1.8 or later. @@ -876,15 +945,15 @@ extrema(itr; kw...) = extrema(identity, itr; kw...) """ extrema(f, itr; [init]) -> (mn, mx) -Compute both the minimum `mn` and maximum `mx` of `f` applied to each element in `itr` and -return them as a 2-tuple. Only one pass is made over `itr`. +Compute both the [`minimum`](@ref) `mn` and [`maximum`](@ref) `mx` of `f` applied to each element in `itr` and +return them as a 2-tuple in an efficient manner. -The value returned for empty `itr` can be specified by `init`. It must be a 2-tuple whose -first and second elements are neutral elements for `min` and `max` respectively -(i.e. which are greater/less than or equal to any other element). It is used one or more times for non-empty -collections. Note: it implies that, for empty `itr`, the returned value `(mn, mx)` satisfies -`mn ≥ mx` even though for non-empty `itr` it satisfies `mn ≤ mx`. This is a "paradoxical" -but yet expected result. +The optional `init` keyword argument must be a 2-tuple whose elements are identities +for `min` and `max`, respectively (i.e. which are greater/less than or equal to any +other returned value), and it provides the return value for empty collections. As a consequence, +the initial minimum must be greater than or equal to the initial maximum, and when +`itr` is empty the returned `(mn, mx)` tuple will have `mn ≥ mx`. It is +an error to call `extrema` with an empty collection without specifying an `init` value. !!! compat "Julia 1.2" This method requires Julia 1.2 or later. From 0107c2b0cc5dd2fb5895570e23c7dd005a72b3ad Mon Sep 17 00:00:00 2001 From: Matt Bauman Date: Wed, 24 Jul 2024 16:41:02 -0400 Subject: [PATCH 15/18] Reword [map]reduce again Try to make each paragraph here "punchier" and more pointed: * First paragraph more specifically describes the general behavior * Second paragraph is about `init`, clarify what it means for `op` args * Third paragraph is about `op`, but point to extended help * Fourth paragraph is about empty collections The extended help now reads clearer with a bulleted list and explicitly mentions argument ordering... and is also a bit more careful about floating point associativity (or the lack thereof) in mapreduce --- base/reduce.jl | 127 +++++++++++++++++++++++++++---------------------- 1 file changed, 70 insertions(+), 57 deletions(-) diff --git a/base/reduce.jl b/base/reduce.jl index 7702f32d76466..afacfffe63760 100644 --- a/base/reduce.jl +++ b/base/reduce.jl @@ -272,24 +272,28 @@ mapreduce_impl(f, op, A::AbstractArrayOrBroadcasted, ifirst::Integer, ilast::Int """ mapreduce(f, op, itrs...; [init]) -Apply function `f` to each element(s) in `itrs`, and then repeatedly call the 2 argument -function `op` with those results or results from previous `op` evaluations until a single value is returned. - -The optional `init` keyword argument must be an identity element for `op`. +Apply function `f` to each element(s) in `itrs` (akin to [`map`](@ref)), +and then repeatedly call the 2 argument function `op` on those results, +`init`, or the result of a previous `op` evaluation until all elements +in the `itrs` have been included in the computation and a single value +is returned (akin to [`@reduce`](@ref)). + +The optional `init` keyword argument must be an identity element for `op` as +it may be included in the reduction one or more times when provided. The `init` value is not transformed by the function `f`. -When `init` is provided, all initial evaluation(s) of `op` use `init` and an -element from `itr` as its arguments with an unspecified argument ordering. -For empty collections, `init` is the return value. It is generally an error to call `mapreduce` -with an empty collection without specifying an `init` value, but in unambiguous cases a known -identity element for `op` may be returned; see [`Base.mapreduce_empty`](@ref) for more details. +Providing `init` ensures that `op` is never called with both its arguments coming from +the mapped `itrs`; it always combines the mapped element(s) with `init` or with the +results of a previous `op` call, or it combines the results of two previous `op` calls. -The reduction function `op` must be an associative operation. In contrast with -[`mapfoldl`](@ref) and [`mapfoldr`](@ref), the sequence of -function evaluations and the associativity of `mapreduce` is -not specified and may vary between different methods and across Julia versions. Some implementations -may reuse the return value of `f` for elements that appear multiple times in the -collection(s). When `itr` is an ordered collection, `mapreduce` preserves the ordering of the -iterator in the reduction and so `op` is not required to be commutative. +The reduction function `op` should be associative but is not required to be commutative. +When the `itrs` are ordered collections, `reduce` preserves the ordering of its elements +in the arguments to `op` from left to right. In contrast to [`mapfoldl`](@ref) and +[`mapfoldr`](@ref), `reduce` does not enforce a particular associativity. See the extended +help for more details. + +For empty collections, `init` is the return value. It is generally an error to call `mapreduce` +with an empty collection without specifying an `init` value, but in some unambiguous cases a known +identity element for `op` may be returned; see [`Base.reduce_empty`](@ref) for more details. Some commonly-used operators may have special implementations of a mapped reduction, and should be used instead: [`maximum`](@ref)`(itr)`, [`minimum`](@ref)`(itr)`, [`sum`](@ref)`(itr)`, @@ -312,28 +316,30 @@ julia> mapreduce(uppercase, *, ['j','u','l','i','a']) ## Arbitrary associativity: examples and consequences The associativity of the reduction is not specified, so the `op` function -must be associative and `init` (if provided) must be an identity element. +should be associative and `init` (if provided) must be an identity element. To demonstrate this, consider the example: ```jldoctest -julia> mapreduce(√, +, [1, 4, 9, 16]; init=0) -10 +julia> mapreduce(√, +, [1, 4, 9, 16]; init=0.0) +10.0 ``` There are many possible ways in which `reduce` might compute this, -including the left-associative `(((0+√1)+√4)+√9)+√16` (like [`foldl`](@ref)) -or the right-associative `√1+(√4+(√9+(√16+0)))` (like [`foldr`](@ref)) -or as a potentially-parallel `((0+√1)+(0+√4))+((0+√9)+(0+√16))`. The exact -strategy does not matter; `reduce` returns `10.0` because `+` is associative -and `0` is a commutative identity element for `+`. Note how the `init` -value may be used one or more times with possibly varying argument orderings and -is not transformed by `√`. - -In contrast, the `-` function is not associative and is not a valid `op` for `reduce`. -Were `-` erroneously used instead of `+` in the above example, the three example -strategies return three different results (`-10.0`, `-2.0` and `0.0`, respectively). - -More subtly, floating point arithmetic is _also_ typically non-associative, +including: + * `(((0 + √1) + √4) + √9) + √16` (left-associative, like [`mapfoldl`](@ref)) + * `√1 + (√4 + (√9 + (√16 + 0)))` (right-associative, like [`mapfoldr`](@ref)) + * `((0 + √1) + (√4 + 0)) + ((0 + √9) + (√16 + 0))` (potentiall parallel) +The exact strategy does not matter; `reduce` returns `10.0` because these additions +are associative and `0` is a commutative identity element for `+`. Note how the `init` +value may be used one or more times with varying argument orderings and +is not transformed by `√`, but the ordering of the values in `[1,4,9,16]` is always maintained. + +In contrast, subtraction is not associative. Were `-` erroneously used instead +of `+` in the above example, `mapreduce` will return an arbitrary value as the +exact strategy is not defined. The three example strategies above yield +three different results (`-10.0`, `-2.0` and `4.0`, respectively). + +More subtly, floating point arithmetic is typically non-associative, even for common operations like `+` that are associative in exact arithmetic. This means that the magnitude of floating-point errors incurred by `mapreduce` are also unspecified. For example, `mapreduce(x->x/10, +, [1, 2, 3, 4])` may return @@ -498,22 +504,26 @@ _mapreduce(f, op, ::IndexCartesian, A::AbstractArrayOrBroadcasted) = mapfoldl(f, """ reduce(op, itr; [init]) -Repeatedly call the 2 argument function `op` with the element(s) in `itr` -or results from previous `op` evaluations until a single value is returned. +Repeatedly call the 2 argument function `op` on the element(s) in the `itr` +collection, `init`, or the result of a previous `op` evaluation until all elements +in `itr` have been included in the computation and a single value is returned. + +The optional `init` keyword argument must be an identity element for `op` as +it may be included in the reduction one or more times when provided. Providing +`init` ensures that `op` is never called with both its arguments coming from +`itr`; it always combines an element in `itr` with `init` or with the results +of a previous `op` call, or it combines the results of two previous `op` calls. + +The reduction function `op` should be associative but is not required to be commutative. +When `itr` is an ordered collection, `reduce` preserves the ordering of its elements +in the arguments to `op` from left to right. In contrast to [`foldl`](@ref) and +[`foldr`](@ref), `reduce` does not enforce a particular associativity. See the +extended help for more details. -The optional `init` keyword argument must be an identity element for `op`. -When `init` is provided, all initial evaluation(s) of `op` use `init` and an -element from `itr` as its arguments with an unspecified argument ordering. For empty collections, `init` is the return value. It is generally an error to call `reduce` -with an empty collection without specifying an `init` value, but in unambiguous cases a known +with an empty collection without specifying an `init` value, but in some unambiguous cases a known identity element for `op` may be returned; see [`Base.reduce_empty`](@ref) for more details. -The reduction function `op` must be an associative operation. In contrast with -[`foldl`](@ref) and [`foldr`](@ref), the associativity of `reduce` is -not specified and may vary between different methods and across Julia versions. -When `itr` is an ordered collection, `reduce` preserves the ordering of the -iterator in the reduction and so `op` is not required to be commutative. - Some commonly-used operators may have special implementations of a reduction, and should be used instead: [`maximum`](@ref)`(itr)`, [`minimum`](@ref)`(itr)`, [`sum`](@ref)`(itr)`, [`prod`](@ref)`(itr)`, [`any`](@ref)`(itr)`, [`all`](@ref)`(itr)`. @@ -523,7 +533,7 @@ should be used instead: [`maximum`](@ref)`(itr)`, [`minimum`](@ref)`(itr)`, [`su julia> reduce(+, [1, 2, 3, 4]; init=0) 10 -julia> reduce(*, Int[]; init=1) +julia> reduce(*, []; init=1) 1 julia> reduce(+, [.1, .2, .3, .4]) ≈ 1.0 @@ -538,7 +548,7 @@ julia> reduce(*, ['J','u','l','i','a']) ## Arbitrary associativity: examples and consequences The associativity of the reduction is not specified, so the `op` function -must be associative and `init` (if provided) must be an identity element. +should be associative and `init` (if provided) must be an identity element. To demonstrate this, consider the example: ```jldoctest @@ -547,16 +557,19 @@ julia> reduce(+, [1, 2, 3, 4]; init=0) ``` There are many possible ways in which `reduce` might compute this, -including the left-associative `(((0+1)+2)+3)+4` (like [`foldl`](@ref)) -or the right-associative `1+(2+(3+(4+0)))` (like [`foldr`](@ref)) -or as a potentially-parallel `((0+1)+(0+2))+((0+3)+(0+4))`. The exact -strategy does not matter; `reduce` returns `10` because `+` is associative -and `0` is a commutative identity element for `+`. Note how the `init` -value may be used one or more times with possibly varying argument orderings. - -In contrast, the `-` function is not associative and is not a valid `op` for `reduce`. -Were `-` erroneously used instead of `+` in the above example, the three example -strategies return three different results (`-10`, `-2` and `0`, respectively). +including: + * `(((0 + 1) + 2) + 3) + 4` (left-associative, like [`foldl`](@ref)) + * `1 + (2 + (3 + (4 + 0)))` (right-associative, like [`foldr`](@ref)) + * `((0 + 1) + (2 + 0)) + ((0 + 3) + (4 + 0))` (potentially parallel) +The exact strategy does not matter; `reduce` returns `10` because integer `+` +is associative and `0` is its identity. Note how the `init` value is used +one or more times with varying argument orderings, but the ordering of the +values in `[1,2,3,4]` is always maintained. + +In contrast, integer subtraction is not associative. Were `-` erroneously +used instead of `+` above, `reduce` will return an arbitrary value as the +exact strategy is not defined. The three example strategies above yield +three different results (`-10`, `-2` and `4`, respectively). More subtly, floating point arithmetic is _also_ typically non-associative, even for common operations like `+` that are associative in exact arithmetic. @@ -594,7 +607,7 @@ arguments, a common return type is found to which all arguments are promoted. The optional `init` keyword argument must be an additive identity (i.e., zero), and it provides the return value for empty collections. It is generally an error to call `sum` -with an empty collection without specifying an `init` value, but in unambiguous cases the +with an empty collection without specifying an `init` value, but in some unambiguous cases the [`zero`](@ref) of the return type may be returned. Like [`mapreduce`](@ref), the associativity of the additions is not specified and the return From f48a2c21ee576a560f7a2608f55e713a3187ca50 Mon Sep 17 00:00:00 2001 From: Matt Bauman Date: Wed, 24 Jul 2024 16:51:44 -0400 Subject: [PATCH 16/18] restore the note on re-use of f's results --- base/reduce.jl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/base/reduce.jl b/base/reduce.jl index afacfffe63760..2ca4810a41c4b 100644 --- a/base/reduce.jl +++ b/base/reduce.jl @@ -288,8 +288,9 @@ results of a previous `op` call, or it combines the results of two previous `op` The reduction function `op` should be associative but is not required to be commutative. When the `itrs` are ordered collections, `reduce` preserves the ordering of its elements in the arguments to `op` from left to right. In contrast to [`mapfoldl`](@ref) and -[`mapfoldr`](@ref), `reduce` does not enforce a particular associativity. See the extended -help for more details. +[`mapfoldr`](@ref), `reduce` does not enforce a particular associativity and some +implementations may reuse the return value of `f` for elements that appear multiple times in +`itrs`. See the extended help for more details. For empty collections, `init` is the return value. It is generally an error to call `mapreduce` with an empty collection without specifying an `init` value, but in some unambiguous cases a known From 141c09cc32a9d9230b2441447ab09bd4bcfbafe4 Mon Sep 17 00:00:00 2001 From: Matt Bauman Date: Wed, 24 Jul 2024 17:25:24 -0400 Subject: [PATCH 17/18] remove ref to non-public Base.reduce_empty MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit and try to fix ≈ docs --- base/reduce.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/base/reduce.jl b/base/reduce.jl index 2ca4810a41c4b..a860bdc1a3fc2 100644 --- a/base/reduce.jl +++ b/base/reduce.jl @@ -294,7 +294,7 @@ implementations may reuse the return value of `f` for elements that appear multi For empty collections, `init` is the return value. It is generally an error to call `mapreduce` with an empty collection without specifying an `init` value, but in some unambiguous cases a known -identity element for `op` may be returned; see [`Base.reduce_empty`](@ref) for more details. +identity element for `op` may be returned. Some commonly-used operators may have special implementations of a mapped reduction, and should be used instead: [`maximum`](@ref)`(itr)`, [`minimum`](@ref)`(itr)`, [`sum`](@ref)`(itr)`, @@ -344,7 +344,7 @@ More subtly, floating point arithmetic is typically non-associative, even for common operations like `+` that are associative in exact arithmetic. This means that the magnitude of floating-point errors incurred by `mapreduce` are also unspecified. For example, `mapreduce(x->x/10, +, [1, 2, 3, 4])` may return -either `1.0` or `0.9999999999999999`, and both are [`≈`](@ref) to `1.0`: +either `1.0` or `0.9999999999999999`, and both are [`≈`](@ref Base.isapprox) to `1.0`: ```jldoctest julia> mapfoldl(x->x/10, +, [1, 2, 3, 4]) @@ -523,7 +523,7 @@ extended help for more details. For empty collections, `init` is the return value. It is generally an error to call `reduce` with an empty collection without specifying an `init` value, but in some unambiguous cases a known -identity element for `op` may be returned; see [`Base.reduce_empty`](@ref) for more details. +identity element for `op` may be returned. Some commonly-used operators may have special implementations of a reduction, and should be used instead: [`maximum`](@ref)`(itr)`, [`minimum`](@ref)`(itr)`, [`sum`](@ref)`(itr)`, @@ -576,7 +576,7 @@ More subtly, floating point arithmetic is _also_ typically non-associative, even for common operations like `+` that are associative in exact arithmetic. This means that the magnitude of floating-point errors incurred by `reduce` are also unspecified. For example, `reduce(+, [.1, .2, .3, .4])` may return -either `1.0` or `0.9999999999999999`, and both are [`≈`](@ref) to `1.0`: +either `1.0` or `0.9999999999999999`, and both are [`≈`](@ref Base.isapprox) to `1.0`: ```jldoctest julia> foldl(+, [.1, .2, .3, .4]) From 15aa2516ac8558ffca7f15a0adee67fb42e03454 Mon Sep 17 00:00:00 2001 From: Matt Bauman Date: Wed, 24 Jul 2024 17:48:20 -0400 Subject: [PATCH 18/18] fix typo in reduce @ref --- base/reduce.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/reduce.jl b/base/reduce.jl index a860bdc1a3fc2..63ac542e48ff8 100644 --- a/base/reduce.jl +++ b/base/reduce.jl @@ -276,7 +276,7 @@ Apply function `f` to each element(s) in `itrs` (akin to [`map`](@ref)), and then repeatedly call the 2 argument function `op` on those results, `init`, or the result of a previous `op` evaluation until all elements in the `itrs` have been included in the computation and a single value -is returned (akin to [`@reduce`](@ref)). +is returned (akin to [`reduce`](@ref)). The optional `init` keyword argument must be an identity element for `op` as it may be included in the reduction one or more times when provided.