|
1 |
| -using Base: promote_eltype |
2 |
| -using Base.Cartesian |
| 1 | +using Base: _default_eltype |
| 2 | +using Compat |
| 3 | + |
3 | 4 | if VERSION >= v"0.6.0-dev.693"
|
4 | 5 | using Base.Broadcast: check_broadcast_indices, broadcast_indices
|
5 | 6 | else
|
|
8 | 9 | const broadcast_indices = broadcast_shape
|
9 | 10 | end
|
10 | 11 |
|
11 |
| -if VERSION >= v"0.5.0-dev+5189" |
12 |
| - _to_shape(dims::Base.DimsOrInds) = map(_to_shape, dims) |
13 |
| - _to_shape(r::Base.OneTo) = Int(last(r)) |
| 12 | +if !isdefined(Base.Broadcast, :ftype) # Julia < 0.6 |
| 13 | + ftype(f, A) = typeof(f) |
| 14 | + ftype(f, A...) = typeof(a -> f(a...)) |
| 15 | + ftype(T::DataType, A) = Type{T} |
| 16 | + ftype(T::DataType, A...) = Type{T} |
14 | 17 | else
|
15 |
| - _to_shape(x) = x |
| 18 | + using Base.Broadcast: ftype |
16 | 19 | end
|
17 | 20 |
|
18 |
| -if VERSION < v"0.5.0-dev+5434" |
19 |
| - function gen_nullcheck(narrays::Int, nd::Int) |
20 |
| - e_nullcheck = macroexpand(:( @nref $nd isnull_1 d->j_d_1 )) |
21 |
| - for k = 2:narrays |
22 |
| - isnull = Symbol("isnull_$k") |
23 |
| - j_d_k = Symbol("j_d_$k") |
24 |
| - e_isnull_k = macroexpand(:( @nref $nd $(isnull) d->$(j_d_k) )) |
25 |
| - e_nullcheck = Expr(:||, e_nullcheck, e_isnull_k) |
26 |
| - end |
27 |
| - return e_nullcheck |
| 21 | +if !isdefined(Base.Broadcast, :ziptype) # Julia < 0.6 |
| 22 | + if isdefined(Base, :Iterators) |
| 23 | + using Base.Iterators: Zip2 |
| 24 | + else |
| 25 | + using Base: Zip2 |
28 | 26 | end
|
| 27 | + ziptype(A) = Tuple{eltype(A)} |
| 28 | + ziptype(A, B) = Zip2{Tuple{eltype(A)}, Tuple{eltype(B)}} |
| 29 | + @inline ziptype(A, B, C, D...) = Zip{Tuple{eltype(A)}, ziptype(B, C, D...)} |
| 30 | +else |
| 31 | + using Base.Broadcast: ziptype |
| 32 | +end |
29 | 33 |
|
30 |
| - function gen_broadcast_body(nd::Int, narrays::Int, f, lift::Bool) |
31 |
| - F = Expr(:quote, f) |
32 |
| - e_nullcheck = gen_nullcheck(narrays, nd) |
33 |
| - if lift |
34 |
| - return quote |
35 |
| - # set up aliases to facilitate subsequent Base.Cartesian magic |
36 |
| - B_isnull = B.isnull |
37 |
| - @nexprs $narrays k->(values_k = A_k.values) |
38 |
| - @nexprs $narrays k->(isnull_k = A_k.isnull) |
39 |
| - # check size |
40 |
| - @assert ndims(B) == $nd |
41 |
| - @ncall $narrays check_broadcast_shape size(B) k->A_k |
42 |
| - # main loops |
43 |
| - @nloops($nd, i, B, |
44 |
| - d->(@nexprs $narrays k->(j_d_k = size(A_k, d) == 1 ? 1 : i_d)), # pre |
45 |
| - begin # body |
46 |
| - if $e_nullcheck |
47 |
| - @inbounds (@nref $nd B_isnull i) = true |
48 |
| - else |
49 |
| - @nexprs $narrays k->(@inbounds v_k = @nref $nd values_k d->j_d_k) |
50 |
| - @inbounds (@nref $nd B i) = (@ncall $narrays $F v) |
51 |
| - end |
52 |
| - end |
53 |
| - ) |
54 |
| - end |
| 34 | +@inline function broadcast_lift(f, x) |
| 35 | + if null_safe_op(f, eltype(x)) |
| 36 | + return @compat Nullable(f(x.value), !isnull(x)) |
| 37 | + else |
| 38 | + U = Core.Inference.return_type(f, Tuple{eltype(x)}) |
| 39 | + if isnull(x) |
| 40 | + return Nullable{U}() |
55 | 41 | else
|
56 |
| - return Base.Broadcast.gen_broadcast_body_cartesian(nd, narrays, f) |
| 42 | + return Nullable(f(unsafe_get(x))) |
57 | 43 | end
|
58 | 44 | end
|
| 45 | +end |
59 | 46 |
|
60 |
| - function gen_broadcast_function(nd::Int, narrays::Int, f, lift::Bool) |
61 |
| - As = [Symbol("A_"*string(i)) for i = 1:narrays] |
62 |
| - body = gen_broadcast_body(nd, narrays, f, lift) |
63 |
| - @eval let |
64 |
| - local _F_ |
65 |
| - function _F_(B, $(As...)) |
66 |
| - $body |
67 |
| - end |
68 |
| - _F_ |
| 47 | +@inline function broadcast_lift(f, x1, x2) |
| 48 | + if null_safe_op(f, eltype(x1), eltype(x2)) |
| 49 | + return @compat Nullable(f(x1.value, x2.value), !(isnull(x1) | isnull(x2))) |
| 50 | + else |
| 51 | + U = Core.Inference.return_type(f, Tuple{eltype(x1), eltype(x2)}) |
| 52 | + if isnull(x1) | isnull(x2) |
| 53 | + return Nullable{U}() |
| 54 | + else |
| 55 | + return Nullable(f(unsafe_get(x1), unsafe_get(x2))) |
69 | 56 | end
|
70 | 57 | end
|
| 58 | +end |
71 | 59 |
|
72 |
| - function Base.broadcast!(f, X::NullableArray; lift::Bool=false) |
73 |
| - broadcast!(f, X, X; lift=lift) |
74 |
| - end |
| 60 | +eltypes() = Tuple{} |
| 61 | +eltypes(x) = Tuple{eltype(x)} |
| 62 | +eltypes(x, xs...) = Tuple{eltype(x), eltypes(xs...).parameters...} |
75 | 63 |
|
76 |
| - @eval let cache = Dict{Any, Dict{Bool, Dict{Int, Dict{Int, Any}}}}() |
77 |
| - """ |
78 |
| - broadcast!(f, B::NullableArray, As::NullableArray...; lift::Bool=false) |
79 |
| -
|
80 |
| - This method implements the same behavior as that of `broadcast!` when called on |
81 |
| - regular `Array` arguments. It also includes the `lift` keyword argument, which |
82 |
| - when set to true will lift `f` over the entries of the `As`. |
83 |
| -
|
84 |
| - Lifting is disabled by default. Note that this method's signature specifies |
85 |
| - the destination `B` array as well as the source `As` arrays as all |
86 |
| - `NullableArray`s. Thus, calling `broadcast!` on a arguments consisting |
87 |
| - of both `Array`s and `NullableArray`s will fall back to the implementation |
88 |
| - of `broadcast!` in `base/broadcast.jl`. |
89 |
| - """ |
90 |
| - function Base.broadcast!(f, B::NullableArray, As::NullableArray...; lift::Bool=false) |
91 |
| - nd = ndims(B) |
92 |
| - narrays = length(As) |
93 |
| - |
94 |
| - cache_f = Base.@get! cache f Dict{Bool, Dict{Int, Dict{Int, Any}}}() |
95 |
| - cache_lift = Base.@get! cache_f lift Dict{Int, Dict{Int, Any}}() |
96 |
| - cache_f_na = Base.@get! cache_lift narrays Dict{Int, Any}() |
97 |
| - func = Base.@get! cache_f_na nd gen_broadcast_function(nd, narrays, f, lift) |
98 |
| - |
99 |
| - func(B, As...) |
100 |
| - return B |
101 |
| - end |
102 |
| - end # let cache |
103 |
| -else |
104 |
| - using Base.Broadcast: newindexer, map_newindexer, newindex |
105 |
| - |
106 |
| - function _nullcheck(nargs) |
107 |
| - nullcheck = :(isnull_1[I_1]) |
108 |
| - for i in 2:nargs |
109 |
| - sym_isnull = Symbol("isnull_$i") |
110 |
| - sym_idx = Symbol("I_$i") |
111 |
| - nullcheck = Expr(:||, :($sym_isnull[$sym_idx]), nullcheck) |
112 |
| - end |
113 |
| - # if 0 argument arrays, treat nullcheck as though it returns false |
114 |
| - nargs >= 1 ? nullcheck : :(false) |
115 |
| - end |
| 64 | +""" |
| 65 | + broadcast_lift(f, xs...) |
116 | 66 |
|
117 |
| - @generated function Base.Broadcast._broadcast!{K,ID,XT,nargs}(f, |
118 |
| - Z::NullableArray, keeps::K, Idefaults::ID, Xs::XT, ::Type{Val{nargs}}; lift=false) |
119 |
| - nullcheck = _nullcheck(nargs) |
120 |
| - quote |
121 |
| - T = eltype(Z) |
122 |
| - $(Expr(:meta, :noinline)) |
123 |
| - # destructure keeps and Xs tuples (common to both lifted and non-lifted broadcast) |
124 |
| - @nexprs $nargs i->(keep_i = keeps[i]) |
125 |
| - @nexprs $nargs i->(Idefault_i = Idefaults[i]) |
126 |
| - if !lift |
127 |
| - # destructure the keeps and As tuples |
128 |
| - @nexprs $nargs i->(X_i = Xs[i]) |
129 |
| - @simd for I in CartesianRange(indices(Z)) |
130 |
| - # reverse-broadcast the indices |
131 |
| - @nexprs $nargs i->(I_i = newindex(I, keep_i, Idefault_i)) |
132 |
| - # extract array values |
133 |
| - @nexprs $nargs i->(@inbounds val_i = X_i[I_i]) |
134 |
| - # call the function and store the result |
135 |
| - @inbounds Z[I] = @ncall $nargs f val |
136 |
| - end |
137 |
| - else |
138 |
| - # destructure the indexmaps and Xs tuples |
139 |
| - @nexprs $nargs i->(values_i = Xs[i].values) |
140 |
| - @nexprs $nargs i->(isnull_i = Xs[i].isnull) |
141 |
| - @simd for I in CartesianRange(indices(Z)) |
142 |
| - # reverse-broadcast the indices |
143 |
| - @nexprs $nargs i->(I_i = newindex(I, keep_i, Idefault_i)) |
144 |
| - if $nullcheck |
145 |
| - # if any args are null, store null |
146 |
| - @inbounds Z.isnull[I] = true |
147 |
| - else |
148 |
| - # extract array values |
149 |
| - @nexprs $nargs i->(@inbounds val_i = values_i[I_i]) |
150 |
| - # call the function and store the result |
151 |
| - @inbounds Z[I] = @ncall $nargs f val |
152 |
| - end |
153 |
| - end |
154 |
| - end |
| 67 | +Lift function `f`, passing it arguments `xs...`, using standard lifting semantics: |
| 68 | +for a function call `f(xs...)`, return null if any `x` in `xs` is null; otherwise, |
| 69 | +return `f` applied to values of `xs`. |
| 70 | +""" |
| 71 | +@inline function broadcast_lift(f, xs...) |
| 72 | + if null_safe_op(f, eltypes(xs).parameters...) |
| 73 | + # TODO: find a more efficient approach than mapreduce |
| 74 | + # (i.e. one which gets lowered to just isnull(x1) | isnull(x2) | ...) |
| 75 | + return @compat Nullable(f(unsafe_get.(xs)...), !mapreduce(isnull, |, xs)) |
| 76 | + else |
| 77 | + U = Core.Inference.return_type(f, eltypes(xs...)) |
| 78 | + # TODO: find a more efficient approach than mapreduce |
| 79 | + # (i.e. one which gets lowered to just isnull(x1) | isnull(x2) | ...) |
| 80 | + if mapreduce(isnull, |, xs) |
| 81 | + return Nullable{U}() |
| 82 | + else |
| 83 | + return Nullable(f(map(unsafe_get, xs)...)) |
155 | 84 | end
|
156 | 85 | end
|
| 86 | +end |
157 | 87 |
|
158 |
| - """ |
159 |
| - broadcast!(f, B::NullableArray, As::NullableArray...; lift::Bool=false) |
160 |
| -
|
161 |
| - This method implements the same behavior as that of `broadcast!` when called |
162 |
| - on regular `Array` arguments. It also includes the `lift` keyword argument, |
163 |
| - which when set to true will lift `f` over the entries of the `As`. |
164 |
| -
|
165 |
| - Lifting is disabled by default. Note that this method's signature specifies |
166 |
| - the destination `B` array as well as the source `As` arrays as all |
167 |
| - `NullableArray`s. Thus, calling `broadcast!` on a arguments consisting of |
168 |
| - both `Array`s and `NullableArray`s will fall back to the implementation of |
169 |
| - `broadcast!` in `base/broadcast.jl`. |
170 |
| - """ |
171 |
| - # Required to solve dispatch ambiguity between |
172 |
| - # broadcast!(f, X::AbstractArray, x::Number...) |
173 |
| - # broadcast!(f, Z::NullableArrays.NullableArray, Xs::NullableArrays.NullableArray...) |
174 |
| - @inline Base.broadcast!(f, Z::NullableArray; lift=false) = |
175 |
| - broadcast!(f, Z, Z; lift=lift) |
176 |
| - |
177 |
| - @inline function Base.broadcast!(f, Z::NullableArray, Xs::NullableArray...; |
178 |
| - lift=false) |
179 |
| - nargs = length(Xs) |
180 |
| - shape = indices(Z) |
181 |
| - check_broadcast_indices(shape, Xs...) |
182 |
| - keeps, Idefaults = map_newindexer(shape, Xs) |
183 |
| - Base.Broadcast._broadcast!(f, Z, keeps, Idefaults, Xs, Val{nargs}; lift=lift) |
184 |
| - return Z |
| 88 | +""" |
| 89 | + broadcast(f, As::NullableArray...) |
| 90 | +
|
| 91 | +Call `broadcast` with nullable lifting semantics and return a `NullableArray`. |
| 92 | +Lifting means calling function `f` on the the values wrapped inside `Nullable` entries |
| 93 | +of the input arrays, and returning null if any entry is missing. |
| 94 | +
|
| 95 | +Note that this method's signature specifies the source `As` arrays as all |
| 96 | +`NullableArray`s. Thus, calling `broadcast` on arguments consisting |
| 97 | +of both `Array`s and `NullableArray`s will fall back to the standard implementation |
| 98 | +of `broadcast` (i.e. without lifting). |
| 99 | +""" |
| 100 | +function Base.broadcast{N}(f, As::Vararg{NullableArray, N}) |
| 101 | + f2(x...) = broadcast_lift(f, x...) |
| 102 | + T = _default_eltype(Base.Generator{ziptype(As...), ftype(f2, As...)}) |
| 103 | + if isleaftype(T) && !(T <: Nullable) |
| 104 | + dest = similar(Array{eltype(T)}, broadcast_indices(As...)) |
| 105 | + else |
| 106 | + dest = similar(NullableArray{eltype(T)}, broadcast_indices(As...)) |
185 | 107 | end
|
| 108 | + invoke(broadcast!, Tuple{Function, AbstractArray, Vararg{AbstractArray, N}}, f2, dest, As...) |
186 | 109 | end
|
187 | 110 |
|
188 | 111 | """
|
189 |
| - broadcast(f, As::NullableArray...;lift::Bool=false) |
| 112 | + broadcast!(f, dest::NullableArray, As::NullableArray...) |
190 | 113 |
|
191 |
| -This method implements the same behavior as that of `broadcast` when called on |
192 |
| -regular `Array` arguments. It also includes the `lift` keyword argument, which |
193 |
| -when set to true will lift `f` over the entries of the `As`. |
| 114 | +Call `broadcast!` with nullable lifting semantics. |
| 115 | +Lifting means calling function `f` on the the values wrapped inside `Nullable` entries |
| 116 | +of the input arrays, and returning null if any entry is missing. |
194 | 117 |
|
195 |
| -Lifting is disabled by default. Note that this method's signature specifies the |
196 |
| -source `As` arrays as all `NullableArray`s. Thus, calling `broadcast!` on |
197 |
| -arguments consisting of both `Array`s and `NullableArray`s will fall back to the |
198 |
| -implementation of `broadcast` in `base/broadcast.jl`. |
| 118 | +Note that this method's signature specifies the destination `dest` array as well as the |
| 119 | +source `As` arrays as all `NullableArray`s. Thus, calling `broadcast!` on a arguments |
| 120 | +consisting of both `Array`s and `NullableArray`s will fall back to the standard implementation |
| 121 | +of `broadcast!` (i.e. without lifting). |
199 | 122 | """
|
200 |
| -@inline function Base.broadcast(f, Xs::NullableArray...;lift::Bool=false) |
201 |
| - return broadcast!(f, NullableArray(eltype(promote_eltype(Xs...)), |
202 |
| - _to_shape(broadcast_indices(Xs...))), |
203 |
| - Xs...; lift=lift) |
| 123 | +function Base.broadcast!{N}(f, dest::NullableArray, As::Vararg{NullableArray, N}) |
| 124 | + f2(x...) = broadcast_lift(f, x...) |
| 125 | + invoke(broadcast!, Tuple{Function, AbstractArray, Vararg{AbstractArray, N}}, f2, dest, As...) |
| 126 | +end |
| 127 | + |
| 128 | +# To fix ambiguity |
| 129 | +function Base.broadcast!(f, dest::NullableArray) |
| 130 | + f2(x...) = broadcast_lift(f, x...) |
| 131 | + invoke(broadcast!, Tuple{Function, AbstractArray, Vararg{AbstractArray, 0}}, f2, dest) |
204 | 132 | end
|
205 | 133 |
|
206 | 134 | # broadcasted ops
|
|
0 commit comments