diff --git a/NEWS.md b/NEWS.md index 0f939ddc5286a..596c63d4188f1 100644 --- a/NEWS.md +++ b/NEWS.md @@ -80,6 +80,10 @@ Library improvements * New `titlecase` function, which capitalizes the first character of each word within a string ([#19469]). + * `any` and `all` now always short-circuit, and `mapreduce` never short-circuits ([#19543]). + That is, not every member of the input iterable will be visited if a `true` (in the case of `any`) or + `false` (in the case of `all`) value is found, and `mapreduce` will visit all members of the iterable. + Compiler/Runtime improvements ----------------------------- diff --git a/base/char.jl b/base/char.jl index 79a1b43dec1e3..a5ca950e02a21 100644 --- a/base/char.jl +++ b/base/char.jl @@ -18,7 +18,7 @@ length(c::Char) = 1 endof(c::Char) = 1 getindex(c::Char) = c getindex(c::Char, i::Integer) = i == 1 ? c : throw(BoundsError()) -getindex(c::Char, I::Integer...) = all(Predicate(x -> x == 1), I) ? c : throw(BoundsError()) +getindex(c::Char, I::Integer...) = all(x -> x == 1, I) ? c : throw(BoundsError()) first(c::Char) = c last(c::Char) = c eltype(::Type{Char}) = Char diff --git a/base/reduce.jl b/base/reduce.jl index 78a7ee3ccf5e6..f8a7c023cb4e3 100644 --- a/base/reduce.jl +++ b/base/reduce.jl @@ -283,54 +283,6 @@ determine the neutral element of `op`. reduce(op, itr) = mapreduce(identity, op, itr) reduce(op, a::Number) = a -### short-circuiting specializations of mapreduce - -## conditions and results of short-circuiting - -immutable Predicate{F} - f::F -end -(pred::Predicate)(x) = pred.f(x)::Bool - -const ShortCircuiting = Union{typeof(&), typeof(|)} - -## short-circuiting (sc) mapreduce definitions - -function mapreduce_sc_impl(f, op::typeof(&), itr) - for x in itr - f(x) || return false - end - return true -end - -function mapreduce_sc_impl(f, op::typeof(|), itr) - for x in itr - f(x) && return true - end - return false -end - -# mapreduce_sc tests if short-circuiting is safe; -# if so, mapreduce_sc_impl is called. If it's not -# safe, call mapreduce_no_sc, which redirects to -# non-short-circuiting definitions. - -mapreduce_no_sc(f, op, itr::Any) = mapfoldl(f, op, itr) -mapreduce_no_sc(f, op, itr::AbstractArray) = _mapreduce(f, op, itr) - -mapreduce_sc(f::Function, op, itr) = mapreduce_no_sc(f, op, itr) -mapreduce_sc(f::Predicate, op, itr) = mapreduce_sc_impl(f, op, itr) - -mapreduce_sc(f::typeof(identity), op, itr) = - eltype(itr) <: Bool ? - mapreduce_sc_impl(f, op, itr) : - mapreduce_no_sc(f, op, itr) - -mapreduce(f, op::ShortCircuiting, n::Number) = n -mapreduce(f, op::ShortCircuiting, itr::AbstractArray) = mapreduce_sc(f,op,itr) -mapreduce(f, op::ShortCircuiting, itr::Any) = mapreduce_sc(f,op,itr) - - ###### Specific reduction functions ###### ## sum @@ -523,7 +475,8 @@ end """ any(itr) -> Bool -Test whether any elements of a boolean collection are `true`. +Test whether any elements of a boolean collection are `true`, returning `true` as +soon as the first `true` value in `itr` is encountered (short-circuiting). ```jldoctest julia> a = [true,false,false,true] @@ -535,6 +488,10 @@ julia> a = [true,false,false,true] julia> any(a) true + +julia> any((println(i); v) for (i, v) in enumerate(a)) +1 +true ``` """ any(itr) = any(identity, itr) @@ -542,7 +499,8 @@ any(itr) = any(identity, itr) """ all(itr) -> Bool -Test whether all elements of a boolean collection are `true`. +Test whether all elements of a boolean collection are `true`, returning `false` as +soon as the first `false` value in `itr` is encountered (short-circuiting). ```jldoctest julia> a = [true,false,false,true] @@ -554,53 +512,65 @@ julia> a = [true,false,false,true] julia> all(a) false + +julia> all((println(i); v) for (i, v) in enumerate(a)) +1 +2 +false ``` """ all(itr) = all(identity, itr) -nonboolean_error(f, op) = throw(ArgumentError(""" - Using non-boolean collections with $f(itr) is not allowed, use - reduce($op, itr) instead. If you are using $f(map(f, itr)) or - $f([f(x) for x in itr]), use $f(f, itr) instead. -""")) -or_bool_only(a, b) = nonboolean_error(:any, :|) -or_bool_only(a::Bool, b::Bool) = a|b -and_bool_only(a, b) = nonboolean_error(:all, :&) -and_bool_only(a::Bool, b::Bool) = a&b - """ any(p, itr) -> Bool -Determine whether predicate `p` returns `true` for any elements of `itr`. +Determine whether predicate `p` returns `true` for any elements of `itr`, returning +`true` as soon as the first item in `itr` for which `p` returns `true` is encountered +(short-circuiting). ```jldoctest julia> any(i->(4<=i<=6), [3,5,7]) true + +julia> any(i -> (println(i); i > 3), 1:10) +1 +2 +3 +4 +true ``` """ -any(f::Any, itr) = any(Predicate(f), itr) -any(f::Predicate, itr) = mapreduce_sc_impl(f, |, itr) -any(f::typeof(identity), itr) = - eltype(itr) <: Bool ? - mapreduce_sc_impl(f, |, itr) : - reduce(or_bool_only, false, itr) +function any(f, itr) + for x in itr + f(x) && return true + end + return false +end """ all(p, itr) -> Bool -Determine whether predicate `p` returns `true` for all elements of `itr`. +Determine whether predicate `p` returns `true` for all elements of `itr`, returning +`false` as soon as the first item in `itr` for which `p` returns `false` is encountered +(short-circuiting). ```jldoctest julia> all(i->(4<=i<=6), [4,5,6]) true + +julia> all(i -> (println(i); i < 3), 1:10) +1 +2 +3 +false ``` """ -all(f::Any, itr) = all(Predicate(f), itr) -all(f::Predicate, itr) = mapreduce_sc_impl(f, &, itr) -all(f::typeof(identity), itr) = - eltype(itr) <: Bool ? - mapreduce_sc_impl(f, &, itr) : - reduce(and_bool_only, true, itr) +function all(f, itr) + for x in itr + f(x) || return false + end + return true +end ## in & contains @@ -629,7 +599,7 @@ julia> 5 in a false ``` """ -in(x, itr) = any(Predicate(y -> y == x), itr) +in(x, itr) = any(y -> y == x, itr) const ∈ = in ∉(x, itr)=!∈(x, itr) diff --git a/test/reduce.jl b/test/reduce.jl index 207e57684a95a..603b07db6f5b7 100644 --- a/test/reduce.jl +++ b/test/reduce.jl @@ -224,6 +224,15 @@ let c = [0, 0], A = 1:1000 @test c == [10,10] end +# 19151 - always short circuit +let c = Int[], d = Int[], A = 1:9 + all((push!(c, x); x < 5) for x in A) + @test c == collect(1:5) + + any((push!(d, x); x > 4) for x in A) + @test d == collect(1:5) +end + # any and all with functors immutable SomeFunctor end