-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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
Limit broadcast mechanism over Nullables #19787
Changes from 4 commits
e0f1cb1
e5c270a
f3aec3b
dcba165
afdd247
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,10 +5,12 @@ module Broadcast | |
using Base.Cartesian | ||
using Base: promote_eltype_op, linearindices, tail, OneTo, to_shape, | ||
_msk_end, unsafe_bitgetindex, bitcache_chunks, bitcache_size, dumpbitcache, | ||
nullable_returntype, null_safe_eltype_op, hasvalue, is_nullable_array | ||
nullable_returntype, null_safe_eltype_op, hasvalue | ||
import Base: broadcast, broadcast! | ||
export broadcast_getindex, broadcast_setindex!, dotview | ||
|
||
typealias ScalarType Union{Type{Any}, Type{Nullable}} | ||
|
||
## Broadcasting utilities ## | ||
# fallbacks for some special cases | ||
@inline broadcast(f, x::Number...) = f(x...) | ||
|
@@ -28,37 +30,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 | ||
|
@@ -132,9 +125,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 | ||
|
@@ -285,28 +276,20 @@ ftype(f, A) = typeof(f) | |
ftype(f, A...) = typeof(a -> f(a...)) | ||
ftype(T::Type, A...) = Type{T} | ||
|
||
# nullables need to be treated like scalars sometimes and like containers | ||
# other times, so there are two variants of typestuple. | ||
|
||
# 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...)}) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
# 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) | ||
|
@@ -317,21 +300,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 | ||
|
@@ -349,7 +325,7 @@ end | |
|
||
Broadcasts the arrays, tuples, `Ref`, nullables, and/or scalars `As` to a | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not from this pull request, but should |
||
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`, | ||
that is not a subtype of `AbstractArray`, `Ref` (except for `Ptr`s), `Tuple` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (Serial/Oxford) comma after |
||
or `Nullable` is considered a scalar. The resulting container is established by | ||
the following rules: | ||
|
||
|
@@ -359,16 +335,9 @@ 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. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not from this pull request, but perhaps "If there is at least an array or a Edit: Perhaps "If the arguments contain at least one array or |
||
|
||
The following additional rules 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. | ||
The following additional rule applies to `Nullable` arguments: If there is at | ||
least a `Nullable`, and all the arguments are scalars or `Nullable`, it returns | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps "at least a" should be "at least one"? |
||
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 | ||
|
@@ -433,21 +402,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...) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -73,7 +73,7 @@ function _noshapecheck_map{Tf,N}(f::Tf, A::SparseVecOrMat, Bs::Vararg{SparseVecO | |
fofzeros = f(_zeros_eltypes(A, Bs...)...) | ||
fpreszeros = fofzeros == zero(fofzeros) | ||
maxnnzC = fpreszeros ? min(length(A), _sumnnzs(A, Bs...)) : length(A) | ||
entrytypeC = Base.Broadcast._broadcast_type(Any, f, A, Bs...) | ||
entrytypeC = Base.Broadcast._broadcast_type(f, A, Bs...) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (should be squashed on merge to avoid broken intermediate commits) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Already squashed it to prevent that. |
||
indextypeC = _promote_indtype(A, Bs...) | ||
C = _allocres(size(A), indextypeC, entrytypeC, maxnnzC) | ||
return fpreszeros ? _map_zeropres!(f, C, A, Bs...) : | ||
|
@@ -101,7 +101,7 @@ function _diffshape_broadcast{Tf,N}(f::Tf, A::SparseVecOrMat, Bs::Vararg{SparseV | |
fofzeros = f(_zeros_eltypes(A, Bs...)...) | ||
fpreszeros = fofzeros == zero(fofzeros) | ||
indextypeC = _promote_indtype(A, Bs...) | ||
entrytypeC = Base.Broadcast._broadcast_type(Any, f, A, Bs...) | ||
entrytypeC = Base.Broadcast._broadcast_type(f, A, Bs...) | ||
shapeC = to_shape(Base.Broadcast.broadcast_indices(A, Bs...)) | ||
maxnnzC = fpreszeros ? _checked_maxnnzbcres(shapeC, A, Bs...) : _densennz(shapeC) | ||
C = _allocres(shapeC, indextypeC, entrytypeC, maxnnzC) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
|
||
|
@@ -409,3 +409,12 @@ Base.Broadcast.broadcast_c(f, ::Type{Array19745}, A, Bs...) = | |
@test isa(aa .+ 1, Array19745) | ||
@test isa(aa .* aa', Array19745) | ||
end | ||
|
||
# broadcast should only "peel off" one container layer | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A simpler way to test this behavior might be nice if you can think of one? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unfortunately I don't have much time for looking for better/simpler tests for today. Maybe we could revisit them another occasion? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That should work fine. Test added. |
||
let io = IOBuffer() | ||
broadcast(x -> print(io, x), [Nullable(1.0)]) | ||
@test String(take!(io)) == "Nullable{Float64}(1.0)" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sounds a bit weird to use string comparison to test the type of the result. Why not e.g. call |
||
v = [] | ||
broadcast(x -> push!(v, x), [Nullable(1)]) | ||
@test get.(v) == get.([Nullable(1)]) | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the
is_nullable_array
import can be removed, and probably the function itself too (inbase/nullable.jl
).