Skip to content

Commit

Permalink
Limit broadcast mechanism over Nullables
Browse files Browse the repository at this point in the history
  • Loading branch information
pabloferz committed Dec 31, 2016
1 parent 26c8d85 commit 5dbc334
Show file tree
Hide file tree
Showing 3 changed files with 25 additions and 108 deletions.
84 changes: 24 additions & 60 deletions base/broadcast.jl
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import Base: broadcast, broadcast!
export bitbroadcast, dotview
export broadcast_getindex, broadcast_setindex!

typealias ScalarType Union{Type{Any}, Type{Nullable}}

## Broadcasting utilities ##
# fallbacks for some special cases
@inline broadcast(f, x::Number...) = f(x...)
Expand All @@ -29,37 +31,28 @@ containertype(::Type) = Any
containertype{T<:Ptr}(::Type{T}) = Any
containertype{T<:Tuple}(::Type{T}) = Tuple
containertype{T<:Ref}(::Type{T}) = Array
containertype{T<:AbstractArray}(::Type{T}) =
is_nullable_array(T) ? Array{Nullable} : Array
containertype{T<:AbstractArray}(::Type{T}) = Array
containertype{T<:Nullable}(::Type{T}) = Nullable
containertype(ct1, ct2) = promote_containertype(containertype(ct1), containertype(ct2))
@inline containertype(ct1, ct2, cts...) = promote_containertype(containertype(ct1), containertype(ct2, cts...))

promote_containertype(::Type{Array}, ::Type{Array}) = Array
promote_containertype(::Type{Array}, ct) = Array
promote_containertype(ct, ::Type{Array}) = Array
promote_containertype(::Type{Tuple}, ::Type{Any}) = Tuple
promote_containertype(::Type{Any}, ::Type{Tuple}) = Tuple
promote_containertype(::Type{Tuple}, ::ScalarType) = Tuple
promote_containertype(::ScalarType, ::Type{Tuple}) = Tuple
promote_containertype(::Type{Any}, ::Type{Nullable}) = Nullable
promote_containertype(::Type{Nullable}, ::Type{Any}) = Nullable
promote_containertype(::Type{Nullable}, ::Type{Array}) = Array{Nullable}
promote_containertype(::Type{Array}, ::Type{Nullable}) = Array{Nullable}
promote_containertype(::Type{Array{Nullable}}, ::Type{Array{Nullable}}) =
Array{Nullable}
promote_containertype(::Type{Array{Nullable}}, ::Type{Array}) = Array{Nullable}
promote_containertype(::Type{Array}, ::Type{Array{Nullable}}) = Array{Nullable}
promote_containertype(::Type{Array{Nullable}}, ct) = Array{Nullable}
promote_containertype(ct, ::Type{Array{Nullable}}) = Array{Nullable}
promote_containertype{T}(::Type{T}, ::Type{T}) = T

## Calculate the broadcast indices of the arguments, or error if incompatible
# array inputs
broadcast_indices() = ()
broadcast_indices(A) = broadcast_indices(containertype(A), A)
broadcast_indices(::Union{Type{Any}, Type{Nullable}}, A) = ()
broadcast_indices(::ScalarType, A) = ()
broadcast_indices(::Type{Tuple}, A) = (OneTo(length(A)),)
broadcast_indices(::Type{Array}, A::Ref) = ()
broadcast_indices{T<:Array}(::Type{T}, A) = indices(A)
broadcast_indices(::Type{Array}, A) = indices(A)
@inline broadcast_indices(A, B...) = broadcast_shape((), broadcast_indices(A), map(broadcast_indices, B)...)
# shape (i.e., tuple-of-indices) inputs
broadcast_shape(shape::Tuple) = shape
Expand Down Expand Up @@ -133,9 +126,7 @@ end

Base.@propagate_inbounds _broadcast_getindex(A, I) = _broadcast_getindex(containertype(A), A, I)
Base.@propagate_inbounds _broadcast_getindex(::Type{Array}, A::Ref, I) = A[]
Base.@propagate_inbounds _broadcast_getindex(::Type{Any}, A, I) = A
Base.@propagate_inbounds _broadcast_getindex(::Union{Type{Any},
Type{Nullable}}, A, I) = A
Base.@propagate_inbounds _broadcast_getindex(::ScalarType, A, I) = A
Base.@propagate_inbounds _broadcast_getindex(::Any, A, I) = A[I]

## Broadcasting core
Expand Down Expand Up @@ -292,22 +283,21 @@ ftype(T::Type, A...) = Type{T}
# if the first argument is Any, then Nullable should be treated like a
# scalar; if the first argument is Array, then Nullable should be treated
# like a container.
typestuple(::Type, a) = (Base.@_pure_meta; Tuple{eltype(a)})
typestuple(::Type{Any}, a::Nullable) = (Base.@_pure_meta; Tuple{typeof(a)})
typestuple(::Type, T::Type) = (Base.@_pure_meta; Tuple{Type{T}})
typestuple{T}(::Type{T}, a, b...) = (Base.@_pure_meta; Tuple{typestuple(T, a).types..., typestuple(T, b...).types...})
typestuple(a) = (Base.@_pure_meta; Tuple{eltype(a)})
typestuple(T::Type) = (Base.@_pure_meta; Tuple{Type{T}})
typestuple(a, b...) = (Base.@_pure_meta; Tuple{typestuple(a).types..., typestuple(b...).types...})

# these functions take the variant of typestuple to be used as first argument
ziptype{T}(::Type{T}, A) = typestuple(T, A)
ziptype{T}(::Type{T}, A, B) = (Base.@_pure_meta; Iterators.Zip2{typestuple(T, A), typestuple(T, B)})
@inline ziptype{T}(::Type{T}, A, B, C, D...) = Iterators.Zip{typestuple(T, A), ziptype(T, B, C, D...)}
ziptype(A) = typestuple(A)
ziptype(A, B) = (Base.@_pure_meta; Iterators.Zip2{typestuple(A), typestuple(B)})
@inline ziptype(A, B, C, D...) = Iterators.Zip{typestuple(A), ziptype(B, C, D...)}

_broadcast_type{S}(::Type{S}, f, T::Type, As...) = Base._return_type(f, typestuple(S, T, As...))
_broadcast_type{T}(::Type{T}, f, A, Bs...) = Base._default_eltype(Base.Generator{ziptype(T, A, Bs...), ftype(f, A, Bs...)})
_broadcast_type(f, T::Type, As...) = Base._return_type(f, typestuple(T, As...))
_broadcast_type(f, A, Bs...) = Base._default_eltype(Base.Generator{ziptype(A, Bs...), ftype(f, A, Bs...)})

# broadcast methods that dispatch on the type of the final container
@inline function broadcast_c(f, ::Type{Array}, A, Bs...)
T = _broadcast_type(Any, f, A, Bs...)
T = _broadcast_type(f, A, Bs...)
shape = broadcast_indices(A, Bs...)
iter = CartesianRange(shape)
if isleaftype(T)
Expand All @@ -318,21 +308,14 @@ _broadcast_type{T}(::Type{T}, f, A, Bs...) = Base._default_eltype(Base.Generator
end
return broadcast_t(f, Any, shape, iter, A, Bs...)
end
@inline function broadcast_c(f, ::Type{Array{Nullable}}, A, Bs...)
@inline rec(x) = broadcast(f, x)
@inline rec(x, y) = broadcast(f, x, y)
@inline rec(x, y, z) = broadcast(f, x, y, z)
@inline rec(xs...) = broadcast(f, xs...)
broadcast_c(rec, Array, A, Bs...)
end
function broadcast_c(f, ::Type{Tuple}, As...)
shape = broadcast_indices(As...)
n = length(shape[1])
return ntuple(k->f((_broadcast_getindex(A, k) for A in As)...), n)
end
@inline function broadcast_c(f, ::Type{Nullable}, a...)
nonnull = all(hasvalue, a)
S = _broadcast_type(Array, f, a...)
S = _broadcast_type(f, a...)
if isleaftype(S) && null_safe_eltype_op(f, a...)
Nullable{S}(f(map(unsafe_get, a)...), nonnull)
else
Expand All @@ -350,8 +333,8 @@ end
Broadcasts the arrays, tuples, `Ref`, nullables, and/or scalars `As` to a
container of the appropriate type and dimensions. In this context, anything
that is not a subtype of `AbstractArray`, `Ref` (except for `Ptr`s) or `Tuple`,
or `Nullable` is considered a scalar. The resulting container is established by
that is not a subtype of `AbstractArray`, `Ref` (except for `Ptr`s) or `Tuple`
is considered a scalar. The resulting container is established by
the following rules:
- If all the arguments are scalars, it returns a scalar.
Expand All @@ -360,16 +343,10 @@ the following rules:
(and treats any `Ref` as a 0-dimensional array of its contents and any tuple
as a 1-dimensional array) expanding singleton dimensions.
The following additional rules apply to `Nullable` arguments:
The following additional rule apply to `Nullable` arguments:
- If there is at least a `Nullable`, and all the arguments are scalars or
`Nullable`, it returns a `Nullable`.
- If there is at least an array or a `Ref` with `Nullable` entries, or there
is at least an array or a `Ref` (perhaps with scalar entries instead of
`Nullable` entries) and a nullable, then the result is an array of
`Nullable` entries.
- If there is a tuple and a nullable, the result is an error, as this case is
not currently supported.
`Nullable`, it returns a `Nullable` treating `Nullable`s as "containers".
A special syntax exists for broadcasting: `f.(args...)` is equivalent to
`broadcast(f, args...)`, and nested `f.(g.(args...))` calls are fused into a
Expand Down Expand Up @@ -434,21 +411,8 @@ Nullable{String}("XY")
julia> broadcast(/, 1.0, Nullable(2.0))
Nullable{Float64}(0.5)
julia> [Nullable(1), Nullable(2), Nullable()] .* 3
3-element Array{Nullable{Int64},1}:
3
6
#NULL
julia> [1+im, 2+2im, 3+3im] ./ Nullable{Int}()
3-element Array{Nullable{Complex{Float64}},1}:
#NULL
#NULL
#NULL
julia> Ref(7) .+ Nullable(3)
0-dimensional Array{Nullable{Int64},0}:
10
julia> (1 + im) ./ Nullable{Int}()
Nullable{Complex{Float64}}()
```
"""
@inline broadcast(f, A, Bs...) = broadcast_c(f, containertype(A, Bs...), A, Bs...)
Expand Down
2 changes: 1 addition & 1 deletion test/broadcast.jl
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,7 @@ StrangeType18623(x,y) = (x,y)
let
f(A, n) = broadcast(x -> +(x, n), A)
@test @inferred(f([1.0], 1)) == [2.0]
g() = (a = 1; Base.Broadcast._broadcast_type(Any, x -> x + a, 1.0))
g() = (a = 1; Base.Broadcast._broadcast_type(x -> x + a, 1.0))
@test @inferred(g()) === Float64
end

Expand Down
47 changes: 0 additions & 47 deletions test/nullable.jl
Original file line number Diff line number Diff line change
Expand Up @@ -507,48 +507,6 @@ end
@test Nullable(10.5) ===
@inferred(broadcast(+, 1, 2, Nullable(3), Nullable(4.0), Nullable(1//2)))

# broadcasting for arrays
@test istypeequal(@inferred(broadcast(+, [1, 2, 3], Nullable{Int}(1))),
Nullable{Int}[2, 3, 4])
@test istypeequal(@inferred(broadcast(+, Nullable{Int}[1, 2, 3], 1)),
Nullable{Int}[2, 3, 4])
@test istypeequal(@inferred(broadcast(+, Nullable{Int}[1, 2, 3], Nullable(1))),
Nullable{Int}[2, 3, 4])
@test istypeequal(@inferred(broadcast(+, Nullable{Int}[1, Nullable()], Nullable(1))),
Nullable{Int}[2, Nullable()])
@test istypeequal(@inferred(broadcast(+, Nullable{Int}[Nullable(), 1],
Nullable{Int}())),
Nullable{Int}[Nullable(), Nullable()])
@test istypeequal(@inferred(broadcast(+, Nullable{Int}[Nullable(), 1],
Nullable{Int}[1, Nullable()])),
Nullable{Int}[Nullable(), Nullable()])
@test istypeequal(@inferred(broadcast(+, Nullable{Int}[Nullable(), 1],
Nullable{Int}[Nullable(), 1])),
Nullable{Int}[Nullable(), 2])
@test istypeequal(@inferred(broadcast(+, Nullable{Int}[Nullable(), Nullable()],
Nullable{Int}[1, 2])),
Nullable{Int}[Nullable(), Nullable()])
@test istypeequal(@inferred(broadcast(+, Nullable{Int}[Nullable(), 1],
Nullable{Int}[1])),
Nullable{Int}[Nullable(), 2])
@test istypeequal(@inferred(broadcast(+, Nullable{Float64}[1.0, 2.0],
Nullable{Float64}[1.0 2.0; 3.0 4.0])),
Nullable{Float64}[2.0 3.0; 5.0 6.0])
@test istypeequal(@inferred(broadcast(+, Nullable{Int}[1, 2], [1, 2], 1)),
Nullable{Int}[3, 5])

@test istypeequal(@inferred(broadcast(/, 1, Nullable{Int}[1, 2, 4])),
Nullable{Float64}[1.0, 0.5, 0.25])
@test istypeequal(@inferred(broadcast(muladd, Nullable(2), 42,
[Nullable(1337), Nullable{Int}()])),
Nullable{Int}[1421, Nullable()])

# heterogenous types (not inferrable)
@test istypeequal(broadcast(+, Any[1, 1.0], Nullable(1//2)),
Any[Nullable(3//2), Nullable(1.5)])
@test istypeequal(broadcast(+, Any[Nullable(1) Nullable(1.0)], Nullable(big"1")),
Any[Nullable(big"2") Nullable(big"2.0")])

# test fast path taken
for op in (+, *, -)
for b1 in (false, true)
Expand All @@ -557,11 +515,6 @@ for op in (+, *, -)
@inferred(broadcast(op, Nullable{Int}(1, b1),
Nullable{Int}(2, b2)))
end
A = [1, 2, 3]
res = @inferred(broadcast(op, A, Nullable{Int}(1, b1)))
@test res[1] === Nullable{Int}(op(1, 1), b1)
@test res[2] === Nullable{Int}(op(2, 1), b1)
@test res[3] === Nullable{Int}(op(3, 1), b1)
end
end

Expand Down

0 comments on commit 5dbc334

Please sign in to comment.