Skip to content

WIP Add size to abstract StaticArray type #134

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

Merged
merged 5 commits into from
Apr 21, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 12 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -325,10 +325,10 @@ an `MArray` might be preferable.

Sometimes it might be useful to imbue your own types, having multiple fields,
with vector-like properties. *StaticArrays* can take care of this for you by
allowing you to inherit from `FieldVector{T}`. For example, consider:
allowing you to inherit from `FieldVector{N, T}`. For example, consider:

```julia
immutable Point3D <: FieldVector{Float64}
immutable Point3D <: FieldVector{3, Float64}
x::Float64
y::Float64
z::Float64
Expand All @@ -337,21 +337,22 @@ end

With this type, users can easily access fields to `p = Point3D(x,y,z)` using
`p.x`, `p.y` or `p.z`, or alternatively via `p[1]`, `p[2]`, or `p[3]`. You may
even permute the coordinates with `p[(3,2,1)]`). Furthermore, `Point3D` is a
complete `AbstractVector` implementation where you can add, subtract or scale
vectors, multiply them by matrices (and return the same type), etc.
even permute the coordinates with `p[SVector(3,2,1)]`). Furthermore, `Point3D`
is a complete `AbstractVector` implementation where you can add, subtract or
scale vectors, multiply them by matrices, etc.

It is also worth noting that `FieldVector`s may be mutable or immutable, and
that `setindex!` is defined for use on mutable types. For mutable containers,
you may want to define a default constructor (no inputs) that can be called by
`similar`.
that `setindex!` is defined for use on mutable types. For immutable containers,
you may want to define a method for `similar_type` so that operations leave the
type constant (otherwise they may fall back to `SVector`). For mutable
containers, you may want to define a default constructor (no inputs) and an
appropriate method for `similar`,

### Implementing your own types

You can easily create your own `StaticArray` type, by defining both `Size` (on the
*type*, e.g. `StaticArrays.Size(::Type{Point3D}) = Size(3)`), and linear
You can easily create your own `StaticArray` type, by defining linear
`getindex` (and optionally `setindex!` for mutable types - see
`setindex(::SVector, val, i)` in *MVector.jl* for an example of how to
`setindex(::MArray, val, i)` in *MArray.jl* for an example of how to
achieve this through pointer manipulation). Your type should define a constructor
that takes a tuple of the data (and mutable containers may want to define a
default constructor).
Expand Down
21 changes: 9 additions & 12 deletions src/FieldVector.jl
Original file line number Diff line number Diff line change
@@ -1,29 +1,26 @@
"""
abstract FieldVector{T} <: StaticVector{T}
abstract FieldVector{N, T} <: StaticVector{N, T}

Inheriting from this type will make it easy to create your own vector types. A `FieldVector`
will automatically determine its size from the number of fields, and define `getindex` and
`setindex!` appropriately. An immutable `FieldVector` will be as performant as an `SVector`
of similar length and element type, while a mutable `FieldVector` will behave similarly to
an `MVector`.
will automatically define `getindex` and `setindex!` appropriately. An immutable
`FieldVector` will be as performant as an `SVector` of similar length and element type,
while a mutable `FieldVector` will behave similarly to an `MVector`.

For example:

immutable/type Point3D <: FieldVector{Float64}
immutable/type Point3D <: FieldVector{3, Float64}
x::Float64
y::Float64
z::Float64
end
"""
abstract type FieldVector{T} <: StaticVector{T} end
abstract type FieldVector{N, T} <: StaticVector{N, T} end

# Is this a good idea?? Should people just define constructors that accept tuples?
@inline (::Type{FV}){FV<:FieldVector}(x::Tuple) = FV(x...)
@inline (::Type{FV})(x::Tuple) where {FV <: FieldVector} = FV(x...)

@pure Size{FV<:FieldVector}(::Type{FV}) = Size(nfields(FV))

@inline getindex(v::FieldVector, i::Int) = getfield(v, i)
@inline setindex!(v::FieldVector, x, i::Int) = setfield!(v, i, x)
@propagate_inbounds getindex(v::FieldVector, i::Int) = getfield(v, i)
@propagate_inbounds setindex!(v::FieldVector, x, i::Int) = setfield!(v, i, x)

# See #53
Base.cconvert{T}(::Type{Ptr{T}}, v::FieldVector) = Ref(v)
Expand Down
81 changes: 40 additions & 41 deletions src/MArray.jl
Original file line number Diff line number Diff line change
@@ -1,77 +1,81 @@
"""
MArray{Size, T, L}()
MArray{Size, T, L}(x::NTuple{L, T})
MArray{Size, T, L}(x1, x2, x3, ...)
MArray{S, T, L}()
MArray{S, T, L}(x::NTuple{L, T})
MArray{S, T, L}(x1, x2, x3, ...)


Construct a statically-sized, mutable array `MArray`. The data may optionally be
provided upon construction and can be mutated later. The `Size` parameter is a
Tuple specifying the dimensions of the array. The `L` parameter is the `length`
of the array and is always equal to `prod(S)`. Constructors may drop the `L` and
`T` parameters if they are inferrable from the input (e.g. `L` is always
inferrable from `Size`).
provided upon construction and cannot be mutated later. The `S` parameter is a Tuple-type
specifying the dimensions, or size, of the array - such as `Tuple{3,4,5}` for a 3×4×5-sized
array. The `L` parameter is the `length` of the array and is always equal to `prod(S)`.
Constructors may drop the `L` and `T` parameters if they are inferrable from the input
(e.g. `L` is always inferrable from `S`).

MArray{Size}(a::Array)
MArray{S}(a::Array)

Construct a statically-sized, mutable array of dimensions `Size` using the data from
`a`. The `Size` parameter is mandatory since the size of `a` is unknown to the
compiler (the element type may optionally also be specified).
Construct a statically-sized, mutable array of dimensions `S` (expressed as a `Tuple{...}`)
using the data from `a`. The `S` parameter is mandatory since the size of `a` is unknown to
the compiler (the element type may optionally also be specified).
"""
type MArray{Size, T, N, L} <: StaticArray{T, N}
type MArray{S <: Tuple, T, N, L} <: StaticArray{S, T, N}
data::NTuple{L,T}

function (::Type{MArray{Size,T,N,L}}){Size,T,N,L}(x::NTuple{L,T})
check_array_parameters(Size, T, Val{N}, Val{L})
new{Size,T,N,L}(x)
function (::Type{MArray{S,T,N,L}}){S,T,N,L}(x::NTuple{L,T})
check_array_parameters(S, T, Val{N}, Val{L})
new{S,T,N,L}(x)
end

function (::Type{MArray{Size,T,N,L}}){Size,T,N,L}(x::NTuple{L,Any})
check_array_parameters(Size, T, Val{N}, Val{L})
new{Size,T,N,L}(convert_ntuple(T, x))
function (::Type{MArray{S,T,N,L}}){S,T,N,L}(x::NTuple{L,Any})
check_array_parameters(S, T, Val{N}, Val{L})
new{S,T,N,L}(convert_ntuple(T, x))
end

function (::Type{MArray{Size,T,N,L}}){Size,T,N,L}()
check_array_parameters(Size, T, Val{N}, Val{L})
new{Size,T,N,L}()
function (::Type{MArray{S,T,N,L}}){S,T,N,L}()
check_array_parameters(S, T, Val{N}, Val{L})
new{S,T,N,L}()
end
end

@generated function (::Type{MArray{Size,T,N}}){Size,T,N}(x::Tuple)
@generated function (::Type{MArray{S,T,N}}){S,T,N}(x::Tuple)
return quote
$(Expr(:meta, :inline))
MArray{Size,T,N,$(tuple_prod(Size))}(x)
MArray{S,T,N,$(tuple_prod(S))}(x)
end
end

@generated function (::Type{MArray{Size,T}}){Size,T}(x::Tuple)
@generated function (::Type{MArray{S,T}}){S,T}(x::Tuple)
return quote
$(Expr(:meta, :inline))
MArray{Size,T,$(tuple_length(Size)),$(tuple_prod(Size))}(x)
MArray{S,T,$(tuple_length(S)),$(tuple_prod(S))}(x)
end
end

@generated function (::Type{MArray{Size}}){Size, T <: Tuple}(x::T)
@generated function (::Type{MArray{S}}){S, T <: Tuple}(x::T)
return quote
$(Expr(:meta, :inline))
MArray{Size,$(promote_tuple_eltype(T)),$(tuple_length(Size)),$(tuple_prod(Size))}(x)
MArray{S,$(promote_tuple_eltype(T)),$(tuple_length(S)),$(tuple_prod(S))}(x)
end
end

@generated function (::Type{MArray{Size,T,N}}){Size,T,N}()
@generated function (::Type{MArray{S,T,N}}){S,T,N}()
return quote
$(Expr(:meta, :inline))
MArray{Size, T, N, $(tuple_prod(Size))}()
MArray{S, T, N, $(tuple_prod(S))}()
end
end

@generated function (::Type{MArray{Size,T}}){Size,T}()
@generated function (::Type{MArray{S,T}}){S,T}()
return quote
$(Expr(:meta, :inline))
MArray{Size, T, $(tuple_length(Size)), $(tuple_prod(Size))}()
MArray{S, T, $(tuple_length(S)), $(tuple_prod(S))}()
end
end

@inline MArray(a::StaticArray) = MArray{size_tuple(typeof(a))}(Tuple(a))

# Simplified show for the type
show(io::IO, ::Type{MArray{S, T, N}}) where {S, T, N} = print(io, "MArray{$S,$T,$N}")

# Some more advanced constructor-like functions
@inline one(::Type{MArray{S}}) where {S} = one(MArray{S,Float64,tuple_length(S)})
@inline eye(::Type{MArray{S}}) where {S} = eye(MArray{S,Float64,tuple_length(S)})
Expand All @@ -82,24 +86,19 @@ end
## MArray methods ##
####################

@pure Size{S}(::Type{MArray{S}}) = Size(S)
@pure Size{S,T}(::Type{MArray{S,T}}) = Size(S)
@pure Size{S,T,N}(::Type{MArray{S,T,N}}) = Size(S)
@pure Size{S,T,N,L}(::Type{MArray{S,T,N,L}}) = Size(S)

function getindex(v::MArray, i::Int)
Base.@_inline_meta
v.data[i]
end

@propagate_inbounds setindex!{S,T}(v::MArray{S,T}, val, i::Int) = setindex!(v, convert(T, val), i)
@inline function setindex!{S,T}(v::MArray{S,T}, val::T, i::Int)
@inline function setindex!(v::MArray, val, i::Int)
@boundscheck if i < 1 || i > length(v)
throw(BoundsError())
end

T = eltype(v)
if isbits(T)
unsafe_store!(Base.unsafe_convert(Ptr{T}, Base.data_pointer_from_objref(v)), val, i)
unsafe_store!(Base.unsafe_convert(Ptr{T}, Base.data_pointer_from_objref(v)), convert(T, val), i)
else
# This one is unsafe (#27)
# unsafe_store!(Base.unsafe_convert(Ptr{Ptr{Void}}, Base.data_pointer_from_objref(v.data)), Base.data_pointer_from_objref(val), i)
Expand All @@ -111,7 +110,7 @@ end

@inline Tuple(v::MArray) = v.data

@inline function Base.unsafe_convert{Size,T}(::Type{Ptr{T}}, a::MArray{Size,T})
@inline function Base.unsafe_convert{S,T}(::Type{Ptr{T}}, a::MArray{S,T})
Base.unsafe_convert(Ptr{T}, Base.data_pointer_from_objref(a))
end

Expand Down
9 changes: 4 additions & 5 deletions src/MMatrix.jl
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,12 @@ end
end
end

@inline convert{S1,S2,T}(::Type{MMatrix{S1,S2}}, a::StaticArray{T}) = MMatrix{S1,S2,T}(Tuple(a))
@inline convert{S1,S2,T}(::Type{MMatrix{S1,S2}}, a::StaticArray{<:Any, T}) = MMatrix{S1,S2,T}(Tuple(a))
@inline MMatrix(a::StaticMatrix) = MMatrix{size(typeof(a),1),size(typeof(a),2)}(Tuple(a))

# Simplified show for the type
show(io::IO, ::Type{MMatrix{N, M, T}}) where {N, M, T} = print(io, "MMatrix{$N,$M,$T}")

# Some more advanced constructor-like functions
@inline one{N}(::Type{MMatrix{N}}) = one(MMatrix{N,N})
@inline eye{N}(::Type{MMatrix{N}}) = eye(MMatrix{N,N})
Expand All @@ -64,10 +67,6 @@ end
## MMatrix methods ##
#####################

@pure Size{S1,S2}(::Type{MMatrix{S1,S2}}) = Size(S1, S2)
@pure Size{S1,S2,T}(::Type{MMatrix{S1,S2,T}}) = Size(S1, S2)
@pure Size{S1,S2,T,L}(::Type{MMatrix{S1,S2,T,L}}) = Size(S1, S2)

@propagate_inbounds function getindex{S1,S2,T}(m::MMatrix{S1,S2,T}, i::Int)
#@boundscheck if i < 1 || i > length(m)
# throw(BoundsError(m,i))
Expand Down
12 changes: 6 additions & 6 deletions src/MVector.jl
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,12 @@ compiler (the element type may optionally also be specified).
"""
const MVector{S, T} = MArray{Tuple{S}, T, 1, S}

@inline (::Type{MVector}){S}(x::NTuple{S,Any}) = MVector{S}(x)
@inline (::Type{MVector{S}}){S, T}(x::NTuple{S,T}) = MVector{S,T}(x)
@inline (::Type{MVector{S}}){S, T <: Tuple}(x::T) = MVector{S,promote_tuple_eltype(T)}(x)
@inline MVector(x::NTuple{S,Any}) where {S} = MVector{S}(x)
@inline MVector{S}(x::NTuple{S,T}) where {S, T} = MVector{S, T}(x)
@inline MVector{S}(x::NTuple{S,Any}) where {S} = MVector{S, promote_tuple_eltype(typeof(x))}(x)

# Simplified show for the type
show(io::IO, ::Type{MVector{N, T}}) where {N, T} = print(io, "MVector{$N,$T}")

# Some more advanced constructor-like functions
@inline zeros{N}(::Type{MVector{N}}) = zeros(MVector{N,Float64})
Expand All @@ -28,9 +31,6 @@ const MVector{S, T} = MArray{Tuple{S}, T, 1, S}
## MVector methods ##
#####################

@pure Size{S}(::Type{MVector{S}}) = Size(S)
@pure Size{S,T}(::Type{MVector{S,T}}) = Size(S)

@propagate_inbounds function getindex(v::MVector, i::Int)
v.data[i]
end
Expand Down
72 changes: 35 additions & 37 deletions src/SArray.jl
Original file line number Diff line number Diff line change
@@ -1,72 +1,70 @@
"""
SArray{Size, T, L}(x::NTuple{L, T})
SArray{Size, T, L}(x1, x2, x3, ...)
SArray{S, T, L}(x::NTuple{L, T})
SArray{S, T, L}(x1, x2, x3, ...)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps drop the L from the constructor usage docs here (the docs below go on to suggest that this is the right thing). Is there any reason people should ever need to specify L explicitly?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This I always find a bit weird. In some sense, we are documenting the type and the constructors. The one without T and L is documented below.

The L is necessary to know about whenever you want to specify it as an element type of an array or a member of a struct, so this was my effort to get the user to find out about it's existence.

Though now I notice that I forgot the N!

Copy link
Member

@c42f c42f Apr 21, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, righto. Yes, I find the constructor/type dichotomy odd too. I often wish the doc system would provide more help here, in terms of autodocumenting the type signature docs, in a way which is systematic but not intrusive. Oh ho, look at this trick:

module A

"""
    $(B)

B is a thing with some type parameters
"""
type B{N,T,X,Y,Z}
end

end

help?> A.B
  A.B{N,T,X,Y,Z}

  B is a thing with some type parameters

Kinda close?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still, even if this was neater, it relies on package authors being systematic and good at writing docs...


Construct a statically-sized array `SArray`. Since this type is immutable,
the data must be provided upon construction and cannot be mutated later. The
`Size` parameter is a Tuple specifying the dimensions of the array. The
`L` parameter is the `length` of the array and is always equal to `prod(S)`.
Constructors may drop the `L` and `T` parameters if they are inferrable
from the input (e.g. `L` is always inferrable from `Size`).
Construct a statically-sized array `SArray`. Since this type is immutable, the data must be
provided upon construction and cannot be mutated later. The `S` parameter is a Tuple-type
specifying the dimensions, or size, of the array - such as `Tuple{3,4,5}` for a 3×4×5-sized
array. The `L` parameter is the `length` of the array and is always equal to `prod(S)`.
Constructors may drop the `L` and `T` parameters if they are inferrable from the input
(e.g. `L` is always inferrable from `S`).

SArray{Size}(a::Array)
SArray{S}(a::Array)

Construct a statically-sized array of dimensions `Size` using the data from
`a`. The `Size` parameter is mandatory since the size of `a` is unknown to the
Construct a statically-sized array of dimensions `S` (expressed as a `Tuple{...}`) using
the data from `a`. The `S` parameter is mandatory since the size of `a` is unknown to the
compiler (the element type may optionally also be specified).
"""
immutable SArray{Size, T, N, L} <: StaticArray{T, N}
immutable SArray{S <: Tuple, T, N, L} <: StaticArray{S, T, N}
data::NTuple{L,T}

function (::Type{SArray{Size,T,N,L}}){Size,T,N,L}(x::NTuple{L,T})
check_array_parameters(Size, T, Val{N}, Val{L})
new{Size,T,N,L}(x)
function (::Type{SArray{S, T, N, L}}){S, T, N, L}(x::NTuple{L,T})
check_array_parameters(S, T, Val{N}, Val{L})
new{S, T, N, L}(x)
end

function (::Type{SArray{Size,T,N,L}}){Size,T,N,L}(x::NTuple{L,Any})
check_array_parameters(Size, T, Val{N}, Val{L})
new{Size,T,N,L}(convert_ntuple(T, x))
function (::Type{SArray{S, T, N, L}}){S, T, N, L}(x::NTuple{L,Any})
check_array_parameters(S, T, Val{N}, Val{L})
new{S, T, N, L}(convert_ntuple(T, x))
end
end

@generated function (::Type{SArray{Size,T,N}}){Size <: Tuple,T,N}(x::Tuple)
@generated function (::Type{SArray{S, T, N}}){S <: Tuple, T, N}(x::Tuple)
return quote
$(Expr(:meta, :inline))
SArray{Size,T,N,$(tuple_prod(Size))}(x)
@_inline_meta
SArray{S, T, N, $(tuple_prod(S))}(x)
end
end

@generated function (::Type{SArray{Size,T}}){Size <: Tuple,T}(x::Tuple)
@generated function (::Type{SArray{S, T}}){S <: Tuple, T}(x::Tuple)
return quote
$(Expr(:meta, :inline))
SArray{Size,T,$(tuple_length(Size)),$(tuple_prod(Size))}(x)
@_inline_meta
SArray{S, T, $(tuple_length(S)), $(tuple_prod(S))}(x)
end
end

@generated function (::Type{SArray{Size}}){Size <: Tuple, T <: Tuple}(x::T)
@generated function (::Type{SArray{S}}){S <: Tuple, T <: Tuple}(x::T)
return quote
$(Expr(:meta, :inline))
SArray{Size,$(promote_tuple_eltype(T)),$(tuple_length(Size)),$(tuple_prod(Size))}(x)
@_inline_meta
SArray{S, $(promote_tuple_eltype(T)), $(tuple_length(S)), $(tuple_prod(S))}(x)
end
end

@inline SArray(a::StaticArray) = SArray{size_tuple(a)}(Tuple(a))
@inline SArray(a::StaticArray) = SArray{size_tuple(a)}(Tuple(a)) # TODO fixme

# Simplified show for the type
show(io::IO, ::Type{SArray{S, T, N}}) where {S, T, N} = print(io, "SArray{$S,$T,$N}")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice.


# Some more advanced constructor-like functions
@inline one(::Type{SArray{S}}) where {S} = one(SArray{S,Float64,tuple_length(S)})
@inline eye(::Type{SArray{S}}) where {S} = eye(SArray{S,Float64,tuple_length(S)})
@inline one(::Type{SArray{S,T}}) where {S,T} = one(SArray{S,T,tuple_length(S)})
@inline eye(::Type{SArray{S,T}}) where {S,T} = eye(SArray{S,T,tuple_length(S)})
@inline one(::Type{SArray{S}}) where {S} = one(SArray{S, Float64, tuple_length(S)})
@inline eye(::Type{SArray{S}}) where {S} = eye(SArray{S, Float64, tuple_length(S)})
@inline one(::Type{SArray{S, T}}) where {S, T} = one(SArray{S, T, tuple_length(S)})
@inline eye(::Type{SArray{S, T}}) where {S, T} = eye(SArray{S, T, tuple_length(S)})

####################
## SArray methods ##
####################

@pure Size{S}(::Type{SArray{S}}) = Size(S)
@pure Size{S,T}(::Type{SArray{S,T}}) = Size(S)
@pure Size{S,T,N}(::Type{SArray{S,T,N}}) = Size(S)
@pure Size{S,T,N,L}(::Type{SArray{S,T,N,L}}) = Size(S)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice′


function getindex(v::SArray, i::Int)
Base.@_inline_meta
v.data[i]
Expand Down
Loading