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

Commit 3bc6fa2

Browse files
committed
map for Julia v0.5
Remove metaprogramming from previous regime (though introduce use of generated functions for n arg map) include compatibility for versions <= JuliaLang/julia@a2ce230
1 parent 8135340 commit 3bc6fa2

File tree

2 files changed

+348
-136
lines changed

2 files changed

+348
-136
lines changed

src/map.jl

+176-136
Original file line numberDiff line numberDiff line change
@@ -1,174 +1,214 @@
1-
using Base.Cartesian
1+
if VERSION < v"0.5.0-dev+3294"
2+
include("map0_4.jl")
3+
else
4+
using Base: collect_similar, Generator, ith_all
5+
6+
macro nullcheck(Xs, nargs)
7+
res = :($(Xs)[1].isnull[i])
8+
for i = 2:nargs
9+
e = :($(Xs)[$i].isnull[i])
10+
res = Expr(:||, res, e)
11+
end
12+
return res
13+
end
214

3-
function gen_nullcheck(narrays::Int)
4-
As = [Symbol("A_"*string(i)) for i = 1:narrays]
5-
e_nullcheck = :($(As[1]).isnull[i])
6-
for i = 2:narrays
7-
e_nullcheck = Expr(:||, e_nullcheck, :($(As[i]).isnull[i]))
15+
macro fcall(Xs, nargs)
16+
res = Expr(:call, :f)
17+
for i in 1:nargs
18+
push!(res.args, :($(Xs)[$i].values[i]))
19+
end
20+
return res
821
end
9-
return e_nullcheck
10-
end
1122

12-
function gen_map!_body{F}(narrays::Int, lift::Bool, f::F)
13-
_f = Expr(:quote, f)
14-
e_nullcheck = gen_nullcheck(narrays)
15-
if lift
16-
return quote
17-
for i in 1:length(dest)
18-
if $e_nullcheck
23+
# Base.map!
24+
25+
Base.map!{F}(f::F, X::NullableArray; lift=false) = map!(f, X, X; lift=lift)
26+
function Base.map!{F}(f::F, dest::NullableArray, X::NullableArray; lift=false)
27+
if lift
28+
for (i, j) in zip(eachindex(dest), eachindex(X))
29+
if X.isnull[j]
1930
dest.isnull[i] = true
2031
else
21-
dest[i] = $_f((@ntuple $narrays j->A_j.values[i])...)
32+
dest.isnull[i] = false
33+
dest.values[i] = f(X.values[j])
2234
end
2335
end
24-
end
25-
else
26-
return quote
27-
for i in 1:length(dest)
28-
dest[i] = $_f((@ntuple $narrays j->A_j[i])...)
36+
else
37+
for (i, j) in zip(eachindex(dest), eachindex(X))
38+
dest[i] = f(X[j])
2939
end
3040
end
41+
return dest
3142
end
32-
end
3343

34-
function gen_map_to!_body{F}(_map_to!::Symbol, narrays::Int, f::F)
35-
_f = Expr(:quote, f)
36-
e_nullcheck = gen_nullcheck(narrays)
37-
return quote
38-
@inbounds for i in offs:length(A_1)
39-
if lift
40-
if $e_nullcheck
41-
# we don't need to compute anything if A.isnull[i], since
42-
# the return type is specified by T and by the algorithm in
43-
# 'body'
44+
function Base.map!{F}(f::F, dest::NullableArray, X1::NullableArray,
45+
X2::NullableArray; lift=false)
46+
if lift
47+
for (i, j, k) in zip(eachindex(dest), eachindex(X1), eachindex(X2))
48+
if X1.isnull[j] | X2.isnull[k]
4449
dest.isnull[i] = true
45-
continue
4650
else
47-
v = $_f((@ntuple $narrays j->A_j.values[i])...)
51+
dest.isnull[i] = false
52+
dest.values[i] = f(X1.values[j], X2.values[k])
4853
end
49-
else
50-
v = $_f((@ntuple $narrays j->A_j[i])...)
5154
end
52-
S = typeof(v)
53-
if S !== T && !(S <: T)
54-
R = typejoin(T, S)
55-
new = similar(dest, R)
56-
copy!(new, 1, dest, 1, i - 1)
57-
new[i] = v
58-
return $(_map_to!)(new, i + 1, (@ntuple $narrays j->A_j)...; lift=lift)
55+
else
56+
for (i, j, k) in zip(eachindex(dest), eachindex(X1), eachindex(X2))
57+
dest[i] = f(X1[j], X2[k])
5958
end
60-
dest[i] = v::T
6159
end
6260
return dest
6361
end
64-
end
6562

66-
function gen_map_body{F}(_map_to!::Symbol, narrays::Int, f::F)
67-
_f = Expr(:quote, f)
68-
e_nullcheck = gen_nullcheck(narrays)
69-
if narrays == 1
70-
pre = quote
71-
isempty(A_1) && return isa(f, Type) ? similar(A_1, f) : similar(A_1)
72-
end
73-
else
74-
pre = quote
75-
shape = mapreduce(size, promote_shape, (@ntuple $narrays j->A_j))
76-
prod(shape) == 0 && return similar(A_1, promote_type((@ntuple $narrays j->A_j)...), shape)
63+
function Base.map!{F}(f::F, dest::NullableArray, Xs::NullableArray...; lift=false)
64+
_map!(f, dest, Xs, lift)
65+
end
66+
67+
@generated function _map!{F, N}(f::F, dest::NullableArray, Xs::Tuple{Vararg{NullableArray, N}}, lift)
68+
return quote
69+
if lift
70+
for i in linearindices(Xs[1])
71+
if @nullcheck Xs $N
72+
dest.isnull[i] = true
73+
else
74+
dest.isnull[i] = false
75+
dest.values[i] = @fcall Xs $N
76+
end
77+
end
78+
else
79+
for i in linearindices(Xs[1])
80+
dest[i] = f(ith_all(i, Xs)...)
81+
end
82+
end
83+
return dest
7784
end
7885
end
79-
return quote
80-
$pre
86+
87+
# Base.map
88+
89+
function Base.map(f, X::NullableArray; lift=false)
90+
lift ? _liftedmap(f, X) : collect_similar(X, Generator(f, X))
91+
end
92+
function Base.map(f, X1::NullableArray, X2::NullableArray; lift=false)
93+
lift ? _liftedmap(f, X1, X2) : collect(Generator(f, X1, X2))
94+
end
95+
function Base.map(f, Xs::NullableArray...; lift=false)
96+
lift ? _liftedmap(f, Xs...) : collect(Generator(f, Xs...))
97+
end
98+
99+
function _liftedmap(f, X::NullableArray)
100+
len = length(X)
101+
# if X is empty, fall back on type inference
102+
len > 0 || return NullableArray{Base.return_types(f, (eltype(X),)), 0}()
81103
i = 1
82-
# find first non-null entry in A_1, ... A_narrays
83-
if lift == true
84-
emptyel = $e_nullcheck
85-
while (emptyel && i < length(A_1))
86-
i += 1
87-
emptyel &= $e_nullcheck
88-
end
89-
# if all entries are null, return a similar
90-
i == length(A_1) && return isa(f, Type) ? similar(A_1, f) : similar(A_1)
91-
v = $_f((@ntuple $narrays j->A_j.values[i])...)
92-
else
93-
v = $_f((@ntuple $narrays j->A_j[i])...)
104+
while X.isnull[i]
105+
i += 1
94106
end
95-
dest = similar(A_1, typeof(v))
107+
# if X is all null, fall back on type inference
108+
i <= len || return similar(X, Base.return_types(f, (eltype(X),)))
109+
v = f(X.values[i])
110+
dest = similar(X, typeof(v))
96111
dest[i] = v
97-
return $(_map_to!)(dest, i + 1, (@ntuple $narrays j->A_j)...; lift=lift)
112+
map_to!(f, dest, X, i+1, len)
98113
end
99-
end
100114

101-
function gen_map!_function{F}(narrays::Int, lift::Bool, f::F)
102-
As = [Symbol("A_"*string(i)) for i = 1:narrays]
103-
body = gen_map!_body(narrays, lift, f)
104-
@eval let
105-
local _F_
106-
function _F_(dest, $(As...))
107-
$body
115+
function _liftedmap(f, X1::NullableArray, X2::NullableArray)
116+
len = prod(promote_shape(X1, X2))
117+
len > 0 || return NullableArray{Base.return_types(f, (eltype(X1), eltype(X2))), 0}()
118+
i = 1
119+
while X1.isnull[i] | X2.isnull[i]
120+
i += 1
108121
end
109-
_F_
122+
i <= len || return similar(X1, Base.return_types(f, (eltype(X1), eltype(X2))))
123+
v = f(X1.values[i], X2.values[i])
124+
dest = similar(X1, typeof(v))
125+
dest[i] = v
126+
map_to!(f, dest, X1, X2, i+1, len)
110127
end
111-
end
112-
113-
function gen_map_function{F}(_map_to!::Symbol, narrays::Int, f::F)
114-
As = [Symbol("A_"*string(i)) for i = 1:narrays]
115-
body_map_to! = gen_map_to!_body(_map_to!, narrays, f)
116-
body_map = gen_map_body(_map_to!, narrays, f)
117128

118-
@eval let $_map_to! # create a closure for subsequent calls to $_map_to!
119-
function $(_map_to!){T}(dest::NullableArray{T}, offs, $(As...); lift::Bool=false)
120-
$body_map_to!
121-
end
122-
local _F_
123-
function _F_($(As...); lift::Bool=false)
124-
$body_map
129+
@generated function _liftedmap{N}(f, Xs::Vararg{NullableArray, N})
130+
return quote
131+
shp = size(zip(Xs...))
132+
len = prod(shp)
133+
i = 1
134+
while @nullcheck Xs $N
135+
i += 1
136+
end
137+
i <= len || return similar(X1, Base.return_types(f, tuple([ eltype(X) for X in Xs ])))
138+
v = @fcall Xs $N
139+
dest = similar(Xs[1], typeof(v))
140+
dest[i] = v
141+
map_to!(f, dest, Xs, i+1, len)
125142
end
126-
return _F_
127-
end # let $_map_to!
128-
end
129-
130-
# Base.map!
131-
@eval let cache = Dict{Bool, Dict{Int, Dict{Base.Callable, Function}}}()
132-
@doc """
133-
`map!{F}(f::F, dest::NullableArray, As::AbstractArray...; lift::Bool=false)`
134-
135-
This method implements the same behavior as that of `map!` when called on
136-
regular `Array` arguments. It also includes the `lift` keyword argument, which
137-
when set to true will lift `f` over the entries of the `As`. Lifting is
138-
disabled by default.
139-
""" ->
140-
function Base.map!{F}(f::F, dest::NullableArray, As::AbstractArray...;
141-
lift::Bool=false)
142-
narrays = length(As)
143-
144-
cache_lift = Base.@get! cache lift Dict{Int, Dict{Base.Callable, Function}}()
145-
cache_f = Base.@get! cache_lift narrays Dict{Base.Callable, Function}()
146-
func = Base.@get! cache_f f gen_map!_function(narrays, lift, f)
143+
end
147144

148-
func(dest, As...)
145+
function map_to!{T}(f, dest::NullableArray{T}, X, offs, len)
146+
# map to dest array, checking the type of each result. if a result does not
147+
# match, widen the result type and re-dispatch.
148+
i = offs
149+
while i <= len
150+
@inbounds if X.isnull[i]
151+
i += 1; continue
152+
end
153+
@inbounds el = f(X.values[i])
154+
S = typeof(el)
155+
if S === T || S <: T
156+
@inbounds dest[i] = el::T
157+
i += 1
158+
else
159+
R = typejoin(T, S)
160+
new = similar(dest, R)
161+
copy!(new, 1, dest, 1, i-1)
162+
@inbounds new[i] = el
163+
return map_to!(f, new, X, i+1, len)
164+
end
165+
end
149166
return dest
150167
end
151-
end
152168

153-
Base.map!{F}(f::F, X::NullableArray; lift::Bool=false) = map!(f, X, X; lift=lift)
154-
155-
# Base.map
156-
@eval let cache = Dict{Int, Dict{Base.Callable, Function}}()
157-
@doc """
158-
`map{F}(f::F, As::AbstractArray...; lift::Bool=false)`
159-
160-
This method implements the same behavior as that of `map!` when called on
161-
regular `Array` arguments. It also includes the `lift` keyword argument, which
162-
when set to true will lift `f` over the entries of the `As`. Lifting is
163-
disabled by default.
164-
""" ->
165-
function Base.map{F}(f::F, As::NullableArray...; lift::Bool=false)
166-
narrays = length(As)
167-
_map_to! = gensym()
168-
169-
cache_fs = Base.@get! cache narrays Dict{Base.Callable, Function}()
170-
_map = Base.@get! cache_fs f gen_map_function(_map_to!, narrays, f)
169+
function map_to!{T}(f, dest::NullableArray{T}, X1, X2, offs, len)
170+
i = offs
171+
while i <= len
172+
@inbounds if X1.isnull[i] | X2.isnull[i]
173+
i += 1; continue
174+
end
175+
@inbounds el = f(X1.values[i], X2.values[i])
176+
S = typeof(el)
177+
if S === T || S <: T
178+
@inbounds dest[i] = el::T
179+
i += 1
180+
else
181+
R = typejoin(T, S)
182+
new = similar(dest, R)
183+
copy!(new, 1, dest, 1, i-1)
184+
@inbounds new[i] = el
185+
return map_to!(f, new, X1, X2, i+1, len)
186+
end
187+
end
188+
return dest
189+
end
171190

172-
return _map(As...; lift=lift)
191+
@generated function map_to!{T, N}(f, dest::NullableArray{T}, Xs::Tuple{Vararg{NullableArray, N}}, offs, len)
192+
return quote
193+
i = offs
194+
while i <= len
195+
@inbounds if @nullcheck Xs $N
196+
i += 1; continue
197+
end
198+
@inbounds el = @fcall Xs $N
199+
S = typeof(el)
200+
if S === T || S <: T
201+
@inbounds dest[i] = el::T
202+
i += 1
203+
else
204+
R = typejoin(T, S)
205+
new = similar(dest, R)
206+
copy!(new, 1, dest, 1, i-1)
207+
@inbounds new[i] = el
208+
return map_to!(f, new, Xs, i+1, len)
209+
end
210+
end
211+
return dest
212+
end
173213
end
174214
end

0 commit comments

Comments
 (0)