Skip to content
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

Match setindex!, push!, and append! behavior of AbstractArray Interface #184

Closed
wants to merge 10 commits into from
6 changes: 6 additions & 0 deletions src/lazy.jl
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,9 @@ function Base.showarg(io::IO, s::LazyRows{T}, toplevel) where T
showfields(io, Tuple(components(s)))
toplevel && print(io, " with eltype LazyRow{", T, "}")
end

# "materialize" / "evaluate"
function Base.convert(::Type{T}, c::LazyRow{T}) where {T}
columns, index = getfield(c, 1), getfield(c, 2)
columns[index...]
end
45 changes: 42 additions & 3 deletions src/structarray.jl
Original file line number Diff line number Diff line change
Expand Up @@ -348,24 +348,63 @@ function Base.view(s::StructArray{T, N, C}, I...) where {T, N, C}
StructArray{T}(map(v -> view(v, I...), components(s)))
end

Base.@propagate_inbounds function Base.setindex!(s::StructArray{<:Any, <:Any, <:Any, CartesianIndex{N}}, vals, I::Vararg{Int, N}) where {N}
"""
By default, this package behave just as `<:Base.AbstractArray` in terms of `convert`.
It does not do any "structural" conversion.
Exceptions include `NamedTuples`.
"""
struct Structural{T}
_::T
end

maybe_structural(x) = x

# this exception needs to be removed to comply with `AbstractArray`
maybe_structural(x::NamedTuple) = Structural(x)

function Base.convert(T, vals::Structural)
# TODO more efficient
s = StructVector{T}(undef, 0)
foreachfield(push!, s, vals._)
only(s)
end

Base.@propagate_inbounds function Base.setindex!(s::StructArray{T, <:Any, <:Any, CartesianIndex{N}}, vals, I::Vararg{Int, N}) where {T, N}
setindex!(s, convert(T, maybe_structural(vals)), I...)
end

Base.@propagate_inbounds function Base.setindex!(s::StructArray{T, <:Any, <:Any, Int}, vals, I::Int) where {T}
setindex!(s, convert(T, maybe_structural(vals)), I)
end

Base.@propagate_inbounds function Base.setindex!(s::StructArray{T, <:Any, <:Any, CartesianIndex{N}}, vals::T, I::Vararg{Int, N}) where {T, N}
@boundscheck checkbounds(s, I...)
foreachfield((col, val) -> (@inbounds col[I...] = val), s, vals)
s
end

Base.@propagate_inbounds function Base.setindex!(s::StructArray{<:Any, <:Any, <:Any, Int}, vals, I::Int)
Base.@propagate_inbounds function Base.setindex!(s::StructArray{T, <:Any, <:Any, Int}, vals::T, I::Int) where {T}
@boundscheck checkbounds(s, I)
foreachfield((col, val) -> (@inbounds col[I] = val), s, vals)
s
end

function Base.push!(s::StructVector, vals)
function Base.push!(s::StructVector{T}, vals) where {T}
push!(s, convert(T, maybe_structural(vals)))
end

function Base.push!(s::StructVector{T}, vals::T) where T
foreachfield(push!, s, vals)
return s
end

function Base.append!(s::StructVector, vals::StructVector)
# TODO, more efficient implementation
foldl(push!, vals; init = s)
return s
end

function Base.append!(s::StructVector{T}, vals::StructVector{T}) where T
foreachfield(append!, s, vals)
return s
end
Expand Down
43 changes: 43 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -809,3 +809,46 @@ Base.similar(bc::Broadcast.Broadcasted{Broadcast.ArrayStyle{MyArray}}, ::Type{El
s = StructArray{ComplexF64}((MyArray(rand(2,2)), MyArray(rand(2,2))))
@test_throws MethodError s .+ s
end

# define two types that are "structurally" equivalent:
struct Foo1
x::Int
y::Int
end

struct Foo2
x::Int
y::Int
end

# but not otherwise interchangeable:
Base.convert(::Type{Foo1}, v::Foo2) = Foo1(-v.x, v.x + v.y)
# Base.convert(::Type{Foo2}, v::Foo1) = Foo2(-v.x, v.y + v.x)

# should_be_identity(v::Foo1) = convert(Foo1, convert(Foo2, v))
# should_be_identity(v::Foo2) = convert(Foo2, convert(Foo1, v))

function check_setindex!_convert(TArray)
a = TArray{Foo1}(undef, 1)
a[1] = Foo2(1, 2)
a[1] == Foo1(-1, 3)
end

function check_push!_convert(TArray)
a = TArray{Foo1}(undef, 0)
push!(a, Foo2(1, 2))
a[1] == Foo1(-1, 3)
end

@testset "convert on setindex!/push!" begin
# check behavior of `Base.Array`
@test check_setindex!_convert(Array)
@test check_push!_convert(Vector)

@test check_setindex!_convert(StructArray)
@test check_push!_convert(StructVector)

soa = StructVector{Foo1}(undef, 0)
append!(soa, StructVector([Foo2(1, 2)]))
@test soa[1] == Foo1(-1, 3)
end