Skip to content
This repository was archived by the owner on May 4, 2019. It is now read-only.

Commit f7bfde5

Browse files
committed
Also port map() to lift()
This introduces a small behavior change for consistency with the generic map(): mapping over an empty array returns a NullableArray{Any} when the function does not exist, rather than a NullableArray{Union{}}.
1 parent c285684 commit f7bfde5

File tree

7 files changed

+176
-486
lines changed

7 files changed

+176
-486
lines changed

perf/map.jl

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,33 +15,33 @@ f(x::Float64) = 5 * x
1515

1616
function profile_map()
1717

18-
println("Method: map!(f, dest, src) (0 missing entries, lift=true)")
18+
println("Method: map!(f, dest, src) (0 missing entries)")
1919
print(" for Array{Float64}: ")
2020
map!(f, C, A);
2121
@time map!(f, C, A);
2222
print(" for NullableArray{Float64}: ")
23-
map!(f, Z, X; lift=true);
24-
@time map!(f, Z, X; lift=true);
23+
map!(f, Z, X);
24+
@time map!(f, Z, X);
2525
println()
2626

27-
println("Method: map!(f, dest, src) (~half missing entries, lift=true)")
27+
println("Method: map!(f, dest, src) (~half missing entries)")
2828
print(" for NullableArray{Float64}: ")
29-
map!(f, Z, Y; lift=true);
30-
@time map!(f, Z, Y; lift=true);
29+
map!(f, Z, Y);
30+
@time map!(f, Z, Y);
3131
println()
3232

33-
println("Method: map(f, src) (0 missing entries, lift=true)")
33+
println("Method: map(f, src) (0 missing entries)")
3434
print(" for Array{Float64}: ")
3535
map(f, A);
3636
@time map(f, A);
3737
print(" for NullableArray{Float64}: ")
38-
map(f, X; lift=true);
39-
@time map(f, X; lift=true);
38+
map(f, X);
39+
@time map(f, X);
4040
println()
4141

42-
println("Method: map(f, src) (~half missing entries, lift=true)")
42+
println("Method: map(f, src) (~half missing entries)")
4343
print(" for NullableArray{Float64}: ")
44-
map(f, Y; lift=true);
45-
@time map(f, Y; lift=true);
44+
map(f, Y);
45+
@time map(f, Y);
4646
println()
4747
end

src/NullableArrays.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ include("typedefs.jl")
2525
include("constructors.jl")
2626
include("primitives.jl")
2727
include("indexing.jl")
28+
include("lift.jl")
2829
include("map.jl")
2930
include("nullablevector.jl")
3031
include("operators.jl")

src/broadcast.jl

Lines changed: 17 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -31,67 +31,6 @@ else
3131
using Base.Broadcast: ziptype
3232
end
3333

34-
@inline function broadcast_lift(f, x)
35-
if null_safe_op(f, eltype(x))
36-
return @compat Nullable(f(unsafe_get(x)), !isnull(x))
37-
else
38-
U = Core.Inference.return_type(f, Tuple{eltype(x)})
39-
if isnull(x)
40-
return Nullable{U}()
41-
else
42-
return Nullable(f(unsafe_get(x)))
43-
end
44-
end
45-
end
46-
47-
@inline function broadcast_lift(f, x1, x2)
48-
if null_safe_op(f, eltype(x1), eltype(x2))
49-
return @compat Nullable(f(unsafe_get(x1), unsafe_get(x2)),
50-
!(isnull(x1) | isnull(x2)))
51-
else
52-
U = Core.Inference.return_type(f, Tuple{eltype(x1), eltype(x2)})
53-
if isnull(x1) | isnull(x2)
54-
return Nullable{U}()
55-
else
56-
return Nullable(f(unsafe_get(x1), unsafe_get(x2)))
57-
end
58-
end
59-
end
60-
61-
eltype_nullable(x::Nullable) = eltype(x)
62-
eltype_nullable(x) = typeof(x)
63-
64-
eltypes() = Tuple{}
65-
eltypes(x) = Tuple{eltype_nullable(x)}
66-
eltypes(x, xs...) = Tuple{eltype_nullable(x), eltypes(xs...).parameters...}
67-
68-
hasnulls() = false
69-
hasnulls(x) = isnull(x)
70-
hasnulls(x, xs...) = hasnulls(x) | hasnulls(xs...)
71-
72-
_unsafe_get(x) = (unsafe_get(x),)
73-
_unsafe_get(x, xs...) = (unsafe_get(x), _unsafe_get(xs...)...)
74-
75-
"""
76-
broadcast_lift(f, xs...)
77-
78-
Lift function `f`, passing it arguments `xs...`, using standard lifting semantics:
79-
for a function call `f(xs...)`, return null if any `x` in `xs` is null; otherwise,
80-
return `f` applied to values of `xs`.
81-
"""
82-
@inline function broadcast_lift(f, xs...)
83-
if null_safe_op(f, eltype_nullable.(xs)...)
84-
return @compat Nullable(f(_unsafe_get(xs...)...), !hasnulls(xs...))
85-
else
86-
U = Core.Inference.return_type(f, eltypes(xs...))
87-
if hasnulls(xs...)
88-
return Nullable{U}()
89-
else
90-
return Nullable(f(_unsafe_get(xs...)...))
91-
end
92-
end
93-
end
94-
9534
call_broadcast{F, N}(f::F, dest, As::Vararg{NullableArray, N}) =
9635
invoke(broadcast!, Tuple{Function, AbstractArray, Vararg{AbstractArray, N}}, f, dest, As...)
9736

@@ -109,14 +48,14 @@ of `broadcast` (i.e. without lifting).
10948
"""
11049
function Base.broadcast{F, N}(f::F, As::Vararg{NullableArray, N})
11150
# These definitions are needed to avoid allocation due to splatting
112-
f2(x1) = broadcast_lift(f, x1)
113-
f2(x1, x2) = broadcast_lift(f, x1, x2)
114-
f2(x1, x2, x3) = broadcast_lift(f, x1, x2, x3)
115-
f2(x1, x2, x3, x4) = broadcast_lift(f, x1, x2, x3, x4)
116-
f2(x1, x2, x3, x4, x5) = broadcast_lift(f, x1, x2, x3, x4, x5)
117-
f2(x1, x2, x3, x4, x5, x6) = broadcast_lift(f, x1, x2, x3, x4, x5, x6)
118-
f2(x1, x2, x3, x4, x5, x6, x7) = broadcast_lift(f, x1, x2, x3, x4, x5, x6, x7)
119-
f2(x...) = broadcast_lift(f, x...)
51+
f2(x1) = lift(f, x1)
52+
f2(x1, x2) = lift(f, x1, x2)
53+
f2(x1, x2, x3) = lift(f, x1, x2, x3)
54+
f2(x1, x2, x3, x4) = lift(f, x1, x2, x3, x4)
55+
f2(x1, x2, x3, x4, x5) = lift(f, x1, x2, x3, x4, x5)
56+
f2(x1, x2, x3, x4, x5, x6) = lift(f, x1, x2, x3, x4, x5, x6)
57+
f2(x1, x2, x3, x4, x5, x6, x7) = lift(f, x1, x2, x3, x4, x5, x6, x7)
58+
f2(x...) = lift(f, x...)
12059

12160
T = _default_eltype(Base.Generator{ziptype(As...), ftype(f2, As...)})
12261
if isleaftype(T) && !(T <: Nullable)
@@ -141,20 +80,20 @@ of `broadcast!` (i.e. without lifting).
14180
"""
14281
function Base.broadcast!{F, N}(f::F, dest::NullableArray, As::Vararg{NullableArray, N})
14382
# These definitions are needed to avoid allocation due to splatting
144-
f2(x1) = broadcast_lift(f, x1)
145-
f2(x1, x2) = broadcast_lift(f, x1, x2)
146-
f2(x1, x2, x3) = broadcast_lift(f, x1, x2, x3)
147-
f2(x1, x2, x3, x4) = broadcast_lift(f, x1, x2, x3, x4)
148-
f2(x1, x2, x3, x4, x5) = broadcast_lift(f, x1, x2, x3, x4, x5)
149-
f2(x1, x2, x3, x4, x5, x6) = broadcast_lift(f, x1, x2, x3, x4, x5, x6)
150-
f2(x1, x2, x3, x4, x5, x6, x7) = broadcast_lift(f, x1, x2, x3, x4, x5, x6, x7)
151-
f2(x...) = broadcast_lift(f, x...)
83+
f2(x1) = lift(f, x1)
84+
f2(x1, x2) = lift(f, x1, x2)
85+
f2(x1, x2, x3) = lift(f, x1, x2, x3)
86+
f2(x1, x2, x3, x4) = lift(f, x1, x2, x3, x4)
87+
f2(x1, x2, x3, x4, x5) = lift(f, x1, x2, x3, x4, x5)
88+
f2(x1, x2, x3, x4, x5, x6) = lift(f, x1, x2, x3, x4, x5, x6)
89+
f2(x1, x2, x3, x4, x5, x6, x7) = lift(f, x1, x2, x3, x4, x5, x6, x7)
90+
f2(x...) = lift(f, x...)
15291
call_broadcast(f2, dest, As...)
15392
end
15493

15594
# To fix ambiguity
15695
function Base.broadcast!{F}(f::F, dest::NullableArray)
157-
f2() = broadcast_lift(f)
96+
f2() = lift(f)
15897
call_broadcast(f2, dest)
15998
end
16099

src/lift.jl

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
@inline function lift(f, x)
2+
if null_safe_op(f, eltype(x))
3+
return @compat Nullable(f(unsafe_get(x)), !isnull(x))
4+
else
5+
U = Core.Inference.return_type(f, Tuple{eltype(x)})
6+
if isnull(x)
7+
return Nullable{U}()
8+
else
9+
return Nullable(f(unsafe_get(x)))
10+
end
11+
end
12+
end
13+
14+
@inline function lift(f, x1, x2)
15+
if null_safe_op(f, eltype(x1), eltype(x2))
16+
return @compat Nullable(f(unsafe_get(x1), unsafe_get(x2)),
17+
!(isnull(x1) | isnull(x2)))
18+
else
19+
U = Core.Inference.return_type(f, Tuple{eltype(x1), eltype(x2)})
20+
if isnull(x1) | isnull(x2)
21+
return Nullable{U}()
22+
else
23+
return Nullable(f(unsafe_get(x1), unsafe_get(x2)))
24+
end
25+
end
26+
end
27+
28+
eltype_nullable(x::Nullable) = eltype(x)
29+
eltype_nullable(x) = typeof(x)
30+
31+
eltypes() = Tuple{}
32+
eltypes(x) = Tuple{eltype_nullable(x)}
33+
eltypes(x, xs...) = Tuple{eltype_nullable(x), eltypes(xs...).parameters...}
34+
35+
hasnulls() = false
36+
hasnulls(x) = isnull(x)
37+
hasnulls(x, xs...) = hasnulls(x) | hasnulls(xs...)
38+
39+
_unsafe_get(x) = (unsafe_get(x),)
40+
_unsafe_get(x, xs...) = (unsafe_get(x), _unsafe_get(xs...)...)
41+
42+
"""
43+
lift(f, xs...)
44+
45+
Lift function `f`, passing it arguments `xs...`, using standard lifting semantics:
46+
for a function call `f(xs...)`, return null if any `x` in `xs` is null; otherwise,
47+
return `f` applied to values of `xs`.
48+
"""
49+
@inline function lift(f, xs...)
50+
if null_safe_op(f, eltype_nullable.(xs)...)
51+
return @compat Nullable(f(_unsafe_get(xs...)...), !hasnulls(xs...))
52+
else
53+
U = Core.Inference.return_type(f, eltypes(xs...))
54+
if hasnulls(xs...)
55+
return Nullable{U}()
56+
else
57+
return Nullable(f(_unsafe_get(xs...)...))
58+
end
59+
end
60+
end

0 commit comments

Comments
 (0)