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

Commit

Permalink
map for Julia v0.5
Browse files Browse the repository at this point in the history
Remove metaprogramming from previous regime
(though introduce use of generated functions for n
arg map)

include compatibility for versions <=
JuliaLang/julia@a2ce230
  • Loading branch information
davidagold committed Jul 6, 2016
1 parent 8135340 commit 3bc6fa2
Show file tree
Hide file tree
Showing 2 changed files with 348 additions and 136 deletions.
312 changes: 176 additions & 136 deletions src/map.jl
Original file line number Diff line number Diff line change
@@ -1,174 +1,214 @@
using Base.Cartesian
if VERSION < v"0.5.0-dev+3294"
include("map0_4.jl")
else
using Base: collect_similar, Generator, ith_all

macro nullcheck(Xs, nargs)
res = :($(Xs)[1].isnull[i])
for i = 2:nargs
e = :($(Xs)[$i].isnull[i])
res = Expr(:||, res, e)
end
return res
end

function gen_nullcheck(narrays::Int)
As = [Symbol("A_"*string(i)) for i = 1:narrays]
e_nullcheck = :($(As[1]).isnull[i])
for i = 2:narrays
e_nullcheck = Expr(:||, e_nullcheck, :($(As[i]).isnull[i]))
macro fcall(Xs, nargs)
res = Expr(:call, :f)
for i in 1:nargs
push!(res.args, :($(Xs)[$i].values[i]))
end
return res
end
return e_nullcheck
end

function gen_map!_body{F}(narrays::Int, lift::Bool, f::F)
_f = Expr(:quote, f)
e_nullcheck = gen_nullcheck(narrays)
if lift
return quote
for i in 1:length(dest)
if $e_nullcheck
# Base.map!

Base.map!{F}(f::F, X::NullableArray; lift=false) = map!(f, X, X; lift=lift)
function Base.map!{F}(f::F, dest::NullableArray, X::NullableArray; lift=false)
if lift
for (i, j) in zip(eachindex(dest), eachindex(X))
if X.isnull[j]
dest.isnull[i] = true
else
dest[i] = $_f((@ntuple $narrays j->A_j.values[i])...)
dest.isnull[i] = false
dest.values[i] = f(X.values[j])
end
end
end
else
return quote
for i in 1:length(dest)
dest[i] = $_f((@ntuple $narrays j->A_j[i])...)
else
for (i, j) in zip(eachindex(dest), eachindex(X))
dest[i] = f(X[j])
end
end
return dest
end
end

function gen_map_to!_body{F}(_map_to!::Symbol, narrays::Int, f::F)
_f = Expr(:quote, f)
e_nullcheck = gen_nullcheck(narrays)
return quote
@inbounds for i in offs:length(A_1)
if lift
if $e_nullcheck
# we don't need to compute anything if A.isnull[i], since
# the return type is specified by T and by the algorithm in
# 'body'
function Base.map!{F}(f::F, dest::NullableArray, X1::NullableArray,
X2::NullableArray; lift=false)
if lift
for (i, j, k) in zip(eachindex(dest), eachindex(X1), eachindex(X2))
if X1.isnull[j] | X2.isnull[k]
dest.isnull[i] = true
continue
else
v = $_f((@ntuple $narrays j->A_j.values[i])...)
dest.isnull[i] = false
dest.values[i] = f(X1.values[j], X2.values[k])
end
else
v = $_f((@ntuple $narrays j->A_j[i])...)
end
S = typeof(v)
if S !== T && !(S <: T)
R = typejoin(T, S)
new = similar(dest, R)
copy!(new, 1, dest, 1, i - 1)
new[i] = v
return $(_map_to!)(new, i + 1, (@ntuple $narrays j->A_j)...; lift=lift)
else
for (i, j, k) in zip(eachindex(dest), eachindex(X1), eachindex(X2))
dest[i] = f(X1[j], X2[k])
end
dest[i] = v::T
end
return dest
end
end

function gen_map_body{F}(_map_to!::Symbol, narrays::Int, f::F)
_f = Expr(:quote, f)
e_nullcheck = gen_nullcheck(narrays)
if narrays == 1
pre = quote
isempty(A_1) && return isa(f, Type) ? similar(A_1, f) : similar(A_1)
end
else
pre = quote
shape = mapreduce(size, promote_shape, (@ntuple $narrays j->A_j))
prod(shape) == 0 && return similar(A_1, promote_type((@ntuple $narrays j->A_j)...), shape)
function Base.map!{F}(f::F, dest::NullableArray, Xs::NullableArray...; lift=false)
_map!(f, dest, Xs, lift)
end

@generated function _map!{F, N}(f::F, dest::NullableArray, Xs::Tuple{Vararg{NullableArray, N}}, lift)
return quote
if lift
for i in linearindices(Xs[1])
if @nullcheck Xs $N
dest.isnull[i] = true
else
dest.isnull[i] = false
dest.values[i] = @fcall Xs $N
end
end
else
for i in linearindices(Xs[1])
dest[i] = f(ith_all(i, Xs)...)
end
end
return dest
end
end
return quote
$pre

# Base.map

function Base.map(f, X::NullableArray; lift=false)
lift ? _liftedmap(f, X) : collect_similar(X, Generator(f, X))
end
function Base.map(f, X1::NullableArray, X2::NullableArray; lift=false)
lift ? _liftedmap(f, X1, X2) : collect(Generator(f, X1, X2))
end
function Base.map(f, Xs::NullableArray...; lift=false)
lift ? _liftedmap(f, Xs...) : collect(Generator(f, Xs...))
end

function _liftedmap(f, X::NullableArray)
len = length(X)
# if X is empty, fall back on type inference
len > 0 || return NullableArray{Base.return_types(f, (eltype(X),)), 0}()
i = 1
# find first non-null entry in A_1, ... A_narrays
if lift == true
emptyel = $e_nullcheck
while (emptyel && i < length(A_1))
i += 1
emptyel &= $e_nullcheck
end
# if all entries are null, return a similar
i == length(A_1) && return isa(f, Type) ? similar(A_1, f) : similar(A_1)
v = $_f((@ntuple $narrays j->A_j.values[i])...)
else
v = $_f((@ntuple $narrays j->A_j[i])...)
while X.isnull[i]
i += 1
end
dest = similar(A_1, typeof(v))
# if X is all null, fall back on type inference
i <= len || return similar(X, Base.return_types(f, (eltype(X),)))
v = f(X.values[i])
dest = similar(X, typeof(v))
dest[i] = v
return $(_map_to!)(dest, i + 1, (@ntuple $narrays j->A_j)...; lift=lift)
map_to!(f, dest, X, i+1, len)
end
end

function gen_map!_function{F}(narrays::Int, lift::Bool, f::F)
As = [Symbol("A_"*string(i)) for i = 1:narrays]
body = gen_map!_body(narrays, lift, f)
@eval let
local _F_
function _F_(dest, $(As...))
$body
function _liftedmap(f, X1::NullableArray, X2::NullableArray)
len = prod(promote_shape(X1, X2))
len > 0 || return NullableArray{Base.return_types(f, (eltype(X1), eltype(X2))), 0}()
i = 1
while X1.isnull[i] | X2.isnull[i]
i += 1
end
_F_
i <= len || return similar(X1, Base.return_types(f, (eltype(X1), eltype(X2))))
v = f(X1.values[i], X2.values[i])
dest = similar(X1, typeof(v))
dest[i] = v
map_to!(f, dest, X1, X2, i+1, len)
end
end

function gen_map_function{F}(_map_to!::Symbol, narrays::Int, f::F)
As = [Symbol("A_"*string(i)) for i = 1:narrays]
body_map_to! = gen_map_to!_body(_map_to!, narrays, f)
body_map = gen_map_body(_map_to!, narrays, f)

@eval let $_map_to! # create a closure for subsequent calls to $_map_to!
function $(_map_to!){T}(dest::NullableArray{T}, offs, $(As...); lift::Bool=false)
$body_map_to!
end
local _F_
function _F_($(As...); lift::Bool=false)
$body_map
@generated function _liftedmap{N}(f, Xs::Vararg{NullableArray, N})
return quote
shp = size(zip(Xs...))
len = prod(shp)
i = 1
while @nullcheck Xs $N
i += 1
end
i <= len || return similar(X1, Base.return_types(f, tuple([ eltype(X) for X in Xs ])))
v = @fcall Xs $N
dest = similar(Xs[1], typeof(v))
dest[i] = v
map_to!(f, dest, Xs, i+1, len)
end
return _F_
end # let $_map_to!
end

# Base.map!
@eval let cache = Dict{Bool, Dict{Int, Dict{Base.Callable, Function}}}()
@doc """
`map!{F}(f::F, dest::NullableArray, As::AbstractArray...; lift::Bool=false)`
This method implements the same behavior as that of `map!` when called on
regular `Array` arguments. It also includes the `lift` keyword argument, which
when set to true will lift `f` over the entries of the `As`. Lifting is
disabled by default.
""" ->
function Base.map!{F}(f::F, dest::NullableArray, As::AbstractArray...;
lift::Bool=false)
narrays = length(As)

cache_lift = Base.@get! cache lift Dict{Int, Dict{Base.Callable, Function}}()
cache_f = Base.@get! cache_lift narrays Dict{Base.Callable, Function}()
func = Base.@get! cache_f f gen_map!_function(narrays, lift, f)
end

func(dest, As...)
function map_to!{T}(f, dest::NullableArray{T}, X, offs, len)
# map to dest array, checking the type of each result. if a result does not
# match, widen the result type and re-dispatch.
i = offs
while i <= len
@inbounds if X.isnull[i]
i += 1; continue
end
@inbounds el = f(X.values[i])
S = typeof(el)
if S === T || S <: T
@inbounds dest[i] = el::T
i += 1
else
R = typejoin(T, S)
new = similar(dest, R)
copy!(new, 1, dest, 1, i-1)
@inbounds new[i] = el
return map_to!(f, new, X, i+1, len)
end
end
return dest
end
end

Base.map!{F}(f::F, X::NullableArray; lift::Bool=false) = map!(f, X, X; lift=lift)

# Base.map
@eval let cache = Dict{Int, Dict{Base.Callable, Function}}()
@doc """
`map{F}(f::F, As::AbstractArray...; lift::Bool=false)`
This method implements the same behavior as that of `map!` when called on
regular `Array` arguments. It also includes the `lift` keyword argument, which
when set to true will lift `f` over the entries of the `As`. Lifting is
disabled by default.
""" ->
function Base.map{F}(f::F, As::NullableArray...; lift::Bool=false)
narrays = length(As)
_map_to! = gensym()

cache_fs = Base.@get! cache narrays Dict{Base.Callable, Function}()
_map = Base.@get! cache_fs f gen_map_function(_map_to!, narrays, f)
function map_to!{T}(f, dest::NullableArray{T}, X1, X2, offs, len)
i = offs
while i <= len
@inbounds if X1.isnull[i] | X2.isnull[i]
i += 1; continue
end
@inbounds el = f(X1.values[i], X2.values[i])
S = typeof(el)
if S === T || S <: T
@inbounds dest[i] = el::T
i += 1
else
R = typejoin(T, S)
new = similar(dest, R)
copy!(new, 1, dest, 1, i-1)
@inbounds new[i] = el
return map_to!(f, new, X1, X2, i+1, len)
end
end
return dest
end

return _map(As...; lift=lift)
@generated function map_to!{T, N}(f, dest::NullableArray{T}, Xs::Tuple{Vararg{NullableArray, N}}, offs, len)
return quote
i = offs
while i <= len
@inbounds if @nullcheck Xs $N
i += 1; continue
end
@inbounds el = @fcall Xs $N
S = typeof(el)
if S === T || S <: T
@inbounds dest[i] = el::T
i += 1
else
R = typejoin(T, S)
new = similar(dest, R)
copy!(new, 1, dest, 1, i-1)
@inbounds new[i] = el
return map_to!(f, new, Xs, i+1, len)
end
end
return dest
end
end
end
Loading

0 comments on commit 3bc6fa2

Please sign in to comment.