Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: Make any and all short-circuiting #11774

Merged
merged 3 commits into from
Jul 29, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion base/REPLCompletions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,7 @@ function shell_completions(string, pos)
# Now look at the last thing we parsed
isempty(args.args[end].args) && return UTF8String[], 0:-1, false
arg = args.args[end].args[end]
if all(map(s -> isa(s, AbstractString), args.args[end].args))
if all(s -> isa(s, AbstractString), args.args[end].args)
# Treat this as a path (perhaps give a list of commands in the future as well?)
return complete_path(join(args.args[end].args), pos)
elseif isexpr(arg, :escape) && (isexpr(arg.args[1], :incomplete) || isexpr(arg.args[1], :error))
Expand Down
23 changes: 23 additions & 0 deletions base/deprecated.jl
Original file line number Diff line number Diff line change
Expand Up @@ -738,3 +738,26 @@ function complement!(s::IntSet)
end
complement(s::IntSet) = complement!(copy(s))
export complement, complement!


# 11774
# when removing these deprecations, move them to reduce.jl, remove the depwarns and uncomment the errors.

nonboolean_warning(f, op, status) = """

Using non-boolean collections with $f(itr) is $status, 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.
"""


function nonboolean_any(itr)
depwarn(nonboolean_warning(:any, :|, "deprecated"), :nonboolean_any)
#throw(ArgumentError(nonboolean_warning(:any, :|, "not supported")))
reduce(|, itr)
end

function nonboolean_all(itr)
depwarn(nonboolean_warning(:all, :&, "deprecated"), :nonboolean_all)
#throw(ArgumentError(nonboolean_warning(:all, :&, "not supported")))
reduce(&, itr)
end
14 changes: 14 additions & 0 deletions base/functors.jl
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,20 @@ end
call(f::UnspecializedFun{1}, x) = f.f(x)
call(f::UnspecializedFun{2}, x, y) = f.f(x,y)

# Special purpose functors

type Predicate{F} <: Func{1}
f::F
end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With such definition, we are not able to encapsulate a callable functor (not function) into an instance of Predicate.

For example:

immutable IsPosFun end
call(::IsPosFun, x) = (x > zero(x))

# The following statement would cause an error
Predicate(IsPosFun())

Use of functors is quite important, especially in cases where performance is critical.

call(pred::Predicate, x) = pred.f(x)::Bool


immutable EqX{T} <: Func{1}
x::T
end
EqX{T}(x::T) = EqX{T}(x)

call(f::EqX, y) = f.x == y

#### Bitwise operators ####

Expand Down
2 changes: 1 addition & 1 deletion base/pkg/query.jl
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ end

function check_requirements(reqs::Requires, deps::Dict{ByteString,Dict{VersionNumber,Available}}, fix::Dict)
for (p,vs) in reqs
if !any([(vn in vs) for vn in keys(deps[p])])
if !any(vn->(vn in vs), keys(deps[p]))
remaining_vs = VersionSet()
err_msg = "fixed packages introduce conflicting requirements for $p: \n"
available_list = sort(collect(keys(deps[p])))
Expand Down
112 changes: 70 additions & 42 deletions base/reduce.jl
Original file line number Diff line number Diff line change
Expand Up @@ -155,12 +155,65 @@ end
mapreduce(f, op, A::AbstractArray) = _mapreduce(f, op, A)
mapreduce(f, op, a::Number) = f(a)

mapreduce(f, op::Function, A::AbstractArray) = _mapreduce(f, specialized_binary(op), A)
mapreduce(f, op::Function, A::AbstractArray) = mapreduce(f, specialized_binary(op), A)

reduce(op, v0, itr) = mapreduce(IdFun(), op, v0, itr)
reduce(op, itr) = mapreduce(IdFun(), op, itr)
reduce(op, a::Number) = a

### short-circuiting specializations of mapreduce

## conditions and results of short-circuiting

const ShortCircuiting = Union{AndFun, OrFun}
const ReturnsBool = Union{EqX, Predicate}

shortcircuits(::AndFun, x::Bool) = !x
shortcircuits(::OrFun, x::Bool) = x

shorted(::AndFun) = false
shorted(::OrFun) = true

sc_finish(::AndFun) = true
sc_finish(::OrFun) = false

## short-circuiting (sc) mapreduce definitions

function mapreduce_sc_impl(f, op, itr::AbstractArray)
@inbounds for x in itr
shortcircuits(op, f(x)) && return shorted(op)
end
return sc_finish(op)
end

function mapreduce_sc_impl(f, op, itr)
for x in itr
shortcircuits(op, f(x)) && return shorted(op)
end
return sc_finish(op)
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_sc(specialized_unary(f), op, itr)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might want to add a comment to document what mapreduce_no_sc and mapreduce_sc mean. When I saw the abbreviation sc, I was not able to immediately associate it with short circuit.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lindahua If I change the ## mapreduce definitions to ## short-circuiting (sc) mapreduce definitions, will it be clear enough?

Edit: I did that and added a comment. Good enough?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks clear enough. Thanks.

mapreduce_sc(f::ReturnsBool, op, itr) = mapreduce_sc_impl(f, op, itr)
mapreduce_sc(f::Func{1}, op, itr) = mapreduce_no_sc(f, op, itr)

mapreduce_sc(f::IdFun, 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 ######

Expand Down Expand Up @@ -298,53 +351,28 @@ end

## all & any

function mapfoldl(f, ::AndFun, itr)
for x in itr
!f(x) && return false
end
return true
end

function mapfoldl(f, ::OrFun, itr)
for x in itr
f(x) && return true
end
return false
end

function mapreduce_impl(f, op::AndFun, A::AbstractArray{Bool}, ifirst::Int, ilast::Int)
while ifirst <= ilast
@inbounds x = A[ifirst]
!f(x) && return false
ifirst += 1
end
return true
end
# make sure that the identity function is defined before `any` or `all` are used
function identity end

function mapreduce_impl(f, op::OrFun, A::AbstractArray{Bool}, ifirst::Int, ilast::Int)
while ifirst <= ilast
@inbounds x = A[ifirst]
f(x) && return true
ifirst += 1
end
return false
end

all(a) = mapreduce(IdFun(), AndFun(), a)
any(a) = mapreduce(IdFun(), OrFun(), a)
any(itr) = any(IdFun(), itr)
all(itr) = all(IdFun(), itr)

all(pred::Union{Callable,Func{1}}, a) = mapreduce(pred, AndFun(), a)
any(pred::Union{Callable,Func{1}}, a) = mapreduce(pred, OrFun(), a)
any(f::Any, itr) = any(f === identity? IdFun() : Predicate(f), itr)
any(f::Predicate, itr) = mapreduce_sc_impl(f, OrFun(), itr)
any(f::IdFun, itr) =
eltype(itr) <: Bool?
mapreduce_sc_impl(f, OrFun(), itr) :
nonboolean_any(itr)

all(f::Any, itr) = all(f === identity? IdFun() : Predicate(f), itr)
all(f::Predicate, itr) = mapreduce_sc_impl(f, AndFun(), itr)
all(f::IdFun, itr) =
eltype(itr) <: Bool?
mapreduce_sc_impl(f, AndFun(), itr) :
nonboolean_all(itr)

## in & contains

immutable EqX{T} <: Func{1}
x::T
end
EqX{T}(x::T) = EqX{T}(x)

call(f::EqX, y) = f.x == y
in(x, itr) = any(EqX(x), itr)

const ∈ = in
Expand Down
2 changes: 1 addition & 1 deletion base/subarray.jl
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,7 @@ in(::Int, ::Colon) = true
## Strides
@generated function strides{T,N,P,I}(V::SubArray{T,N,P,I})
Ip = I.parameters
all(map(x->x<:Union{RangeIndex,Colon}, Ip)) || throw(ArgumentError("strides valid only for RangeIndex indexing"))
all(x->x<:Union{RangeIndex,Colon}, Ip) || throw(ArgumentError("strides valid only for RangeIndex indexing"))
strideexprs = Array(Any, N+1)
strideexprs[1] = 1
i = 1
Expand Down
2 changes: 1 addition & 1 deletion examples/queens.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

addqueen(queens::Array{Vector{Int}}, queen::Vector{Int}) = push!(copy(queens), queen)

hitsany(queen::Vector{Int}, queens::Array{Vector{Int}}) = any(map(x->hits(queen, x), queens))
hitsany(queen::Vector{Int}, queens::Array{Vector{Int}}) = any(x->hits(queen, x), queens)
hits(a::Array{Int}, b::Array{Int}) = any(a .== b) || abs(a-b)[1] == abs(a-b)[2]

function solve(x, y, n, d=Array(Vector{Int}, 0))
Expand Down
2 changes: 1 addition & 1 deletion test/bitarray.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This file is a part of Julia. License is MIT: http://julialang.org/license

tc{N}(r1::NTuple{N}, r2::NTuple{N}) = all(map(x->tc(x...), [zip(r1,r2)...]))
tc{N}(r1::NTuple{N}, r2::NTuple{N}) = all(x->tc(x...), [zip(r1,r2)...])
tc{N}(r1::BitArray{N}, r2::Union{BitArray{N},Array{Bool,N}}) = true
tc{T}(r1::T, r2::T) = true
tc(r1,r2) = false
Expand Down
32 changes: 16 additions & 16 deletions test/dates/ranges.jl
Original file line number Diff line number Diff line change
Expand Up @@ -258,17 +258,17 @@ drs2 = map(x->Dates.Date(first(x)):step(x):Dates.Date(last(x)),drs)
@test map(length,drs) == map(x->size(x)[1],drs)
@test map(length,drs) == map(x->length(Dates.Date(first(x)):step(x):Dates.Date(last(x))),drs)
@test map(length,drs) == map(x->length(reverse(x)),drs)
@test all(map(x->findin(x,x)==[1:length(x);],drs[1:4]))
@test all(x->findin(x,x)==[1:length(x);],drs[1:4])
@test isempty(dr2)
@test all(map(x->reverse(x) == range(last(x), -step(x), length(x)),drs))
@test all(map(x->minimum(x) == (step(x) < zero(step(x)) ? last(x) : first(x)),drs[4:end]))
@test all(map(x->maximum(x) == (step(x) < zero(step(x)) ? first(x) : last(x)),drs[4:end]))
@test all(map(drs[1:3]) do dd
@test all(x->reverse(x) == range(last(x), -step(x), length(x)),drs)
@test all(x->minimum(x) == (step(x) < zero(step(x)) ? last(x) : first(x)),drs[4:end])
@test all(x->maximum(x) == (step(x) < zero(step(x)) ? first(x) : last(x)),drs[4:end])
@test all(drs[1:3]) do dd
for (i,d) in enumerate(dd)
@test d == (first(dd) + Dates.Day(i-1))
end
true
end)
end
@test_throws MethodError dr + 1
a = Dates.DateTime(2013,1,1)
b = Dates.DateTime(2013,2,1)
Expand All @@ -283,8 +283,8 @@ b = Dates.DateTime(2013,2,1)
@test Dates.DateTime(2013,1,26) in dr
@test !(Dates.DateTime(2012,1,1) in dr)

@test all(map(x->sort(x) == (step(x) < zero(step(x)) ? reverse(x) : x),drs))
@test all(map(x->step(x) < zero(step(x)) ? issorted(reverse(x)) : issorted(x),drs))
@test all(x->sort(x) == (step(x) < zero(step(x)) ? reverse(x) : x),drs)
@test all(x->step(x) < zero(step(x)) ? issorted(reverse(x)) : issorted(x),drs)

@test length(b:Dates.Day(-1):a) == 32
@test length(b:a) == 0
Expand Down Expand Up @@ -336,17 +336,17 @@ drs = Any[dr,dr1,dr2,dr3,dr4,dr5,dr6,dr7,dr8,dr9,dr10,
dr11,dr12,dr13,dr14,dr15,dr16,dr17,dr18,dr19,dr20]

@test map(length,drs) == map(x->size(x)[1],drs)
@test all(map(x->findin(x,x) == [1:length(x);], drs[1:4]))
@test all(x->findin(x,x) == [1:length(x);], drs[1:4])
@test isempty(dr2)
@test all(map(x->reverse(x) == last(x):-step(x):first(x),drs))
@test all(map(x->minimum(x) == (step(x) < zero(step(x)) ? last(x) : first(x)),drs[4:end]))
@test all(map(x->maximum(x) == (step(x) < zero(step(x)) ? first(x) : last(x)),drs[4:end]))
@test all(map(drs[1:3]) do dd
@test all(x->reverse(x) == last(x):-step(x):first(x),drs)
@test all(x->minimum(x) == (step(x) < zero(step(x)) ? last(x) : first(x)),drs[4:end])
@test all(x->maximum(x) == (step(x) < zero(step(x)) ? first(x) : last(x)),drs[4:end])
@test all(drs[1:3]) do dd
for (i,d) in enumerate(dd)
@test d == (first(dd) + Dates.Day(i-1))
end
true
end)
end
@test_throws MethodError dr + 1
a = Dates.Date(2013,1,1)
b = Dates.Date(2013,2,1)
Expand All @@ -361,8 +361,8 @@ b = Dates.Date(2013,2,1)
@test Dates.Date(2013,1,26) in dr
@test !(Dates.Date(2012,1,1) in dr)

@test all(map(x->sort(x) == (step(x) < zero(step(x)) ? reverse(x) : x),drs))
@test all(map(x->step(x) < zero(step(x)) ? issorted(reverse(x)) : issorted(x),drs))
@test all(x->sort(x) == (step(x) < zero(step(x)) ? reverse(x) : x),drs)
@test all(x->step(x) < zero(step(x)) ? issorted(reverse(x)) : issorted(x),drs)

@test length(b:Dates.Day(-1):a) == 32
@test length(b:a) == 0
Expand Down
21 changes: 21 additions & 0 deletions test/reduce.jl
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,27 @@ prod2(itr) = invoke(prod, Tuple{Any}, itr)
@test reduce(&, fill(trues(5), 24)) == trues(5)
@test reduce(&, fill(falses(5), 24)) == falses(5)

@test_throws TypeError any(x->0, [false])
@test_throws TypeError all(x->0, [false])

# short-circuiting any and all

let c = [0, 0], A = 1:1000
any(x->(c[1]=x; x==10), A)
all(x->(c[2]=x; x!=10), A)

@test c == [10,10]
end

# any and all with functors

immutable SomeFunctor end
Base.call(::SomeFunctor, x) = true

@test any(SomeFunctor(), 1:10)
@test all(SomeFunctor(), 1:10)


# in

@test in(1, Int[]) == false
Expand Down