Skip to content

Commit

Permalink
Rewrite all @pure functions (#105)
Browse files Browse the repository at this point in the history
 * Now we use `Size` more consistently and rely less on `@pure` implementations of `similar_type`.

 * `similar` and `similar_type` both use `Size` not integers to discuss size

 * `similar` and `similar_type` are now streamlined. Users only need to
   override one definition, just like in Base.

 * Associated documentation and test changes.

 * Added some more tests
  • Loading branch information
andyferris committed Feb 20, 2017
1 parent 1be47cc commit e6d7387
Show file tree
Hide file tree
Showing 29 changed files with 429 additions and 526 deletions.
69 changes: 38 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,37 +141,6 @@ the methods in Base, we seek to provide a comprehensive support for statically
sized arrays, large or small, that hopefully "just works".

## API Details
### Indexing

Statically sized indexing can be realized by indexing each dimension by a
scalar, an `NTuple{N, Integer}` or `:` (on statically sized arrays only).
Indexing in this way will result a statically sized array (even if the input was
dynamically sized) of the closest type (as defined by `similar_type`).

Conversely, indexing a statically sized array with a dynamically sized index
(such as a `Vector{Integer}` or `UnitRange{Integer}`) will result in a standard
(dynamically sized) `Array`.

### `similar_type()`

Since immutable arrays need to be constructed "all-at-once", we need a way of
obtaining an appropriate constructor if the element type or dimensions of the
output array differs from the input. To this end, `similar_type` is introduced,
behaving just like `similar`, except that it returns a type. Relevant methods
are:

```julia
similar_type{A <: StaticArray}(::Type{A}) # defaults to A
similar_type{A <: StaticArray, ElType}(::Type{A}, ::Type{ElType}) # Change element type
similar_type{A <: StaticArray}(::Type{A}, size::Tuple{Int...}) # Change size
similar_type{A <: StaticArray, ElType}(::Type{A}, ::Type{ElType}, size::Tuple{Int...}) # Change both
```

These setting will affect everything, from indexing, to matrix multiplication
and `broadcast`.

Use of `similar` will fall back to a mutable container, such as a `MVector`
(see below).

### The `Size` trait

Expand Down Expand Up @@ -203,6 +172,44 @@ reshape(svector, Size(2,2)) # Convert SVector{4} to SMatrix{2,2}
Size(3,3)(rand(3,3)) # Construct a random 3×3 SizedArray (see below)
```

Users that introduce a new subtype of `StaticArray` should define a (`@pure`)
method for `Size(::Type{NewArrayType})`.

### Indexing

Statically sized indexing can be realized by indexing each dimension by a
scalar, a `StaticVector` or `:`. Indexing in this way will result a statically
sized array (even if the input was dynamically sized, in the case of
`StaticVector` indices) of the closest type (as defined by `similar_type`).

Conversely, indexing a statically sized array with a dynamically sized index
(such as a `Vector{Integer}` or `UnitRange{Integer}`) will result in a standard
(dynamically sized) `Array`.

### `similar_type()`

Since immutable arrays need to be constructed "all-at-once", we need a way of
obtaining an appropriate constructor if the element type or dimensions of the
output array differs from the input. To this end, `similar_type` is introduced,
behaving just like `similar`, except that it returns a type. Relevant methods
are:

```julia
similar_type{A <: StaticArray}(::Type{A}) # defaults to A
similar_type{A <: StaticArray, ElType}(::Type{A}, ::Type{ElType}) # Change element type
similar_type{A <: AbstractArray}(::Type{A}, size::Size) # Change size
similar_type{A <: AbstractArray, ElType}(::Type{A}, ::Type{ElType}, size::Size) # Change both
```

These setting will affect everything, from indexing, to matrix multiplication
and `broadcast`. Users wanting introduce a new array type should *only* overload
the last method in the above.

Use of `similar` will fall back to a mutable container, such as a `MVector`
(see below), and it requires use of the `Size` trait if you wish to set a new
static size (or else a dynamically sized `Array` will be generated when
specifying the size as plain integers).

### `SVector`

The simplest static array is the `SVector`, defined as
Expand Down
16 changes: 6 additions & 10 deletions src/FieldVector.jl
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
"""
abstract FieldVector{T} <: StaticVector{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
(or it can be overriden by `size()`), 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`.
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`.
For example:
Expand All @@ -21,10 +20,7 @@ abstract FieldVector{T} <: StaticVector{T}
# Is this a good idea?? Should people just define constructors that accept tuples?
@inline (::Type{FV}){FV<:FieldVector}(x::Tuple) = FV(x...)

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

@inline getindex(v::FieldVector, i::Integer) = getfield(v, i)
@inline setindex!(v::FieldVector, x, i::Integer) = setfield!(v, i, x)
Expand Down
28 changes: 11 additions & 17 deletions src/FixedSizeArrays.jl
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,8 @@ macro fixed_vector(name, parent)
end
end

Base.@pure Base.size{S}(::Union{$(name){S}, Type{$(name){S}}}) = (S, )
Base.@pure Base.size{S,T}(::Type{$(name){S, T}}) = (S,)
Base.@pure StaticArrays.Size{S}(::Type{$(name){S}}) = Size(S)
Base.@pure StaticArrays.Size{S,T}(::Type{$(name){S, T}}) = Size(S)

Base.@propagate_inbounds function Base.getindex(v::$(name), i::Integer)
v.data[i]
Expand All @@ -144,22 +144,16 @@ macro fixed_vector(name, parent)
@inline function Base.convert{S, T}(::Type{$(name){S, T}}, x::Tuple)
$(name){S, T}(convert(NTuple{S, T}, x))
end
# StaticArrays.similar_type{SV <: $(name)}(::Union{SV, Type{SV}}) = $(name)
# function StaticArrays.similar_type{SV <: $(name), T}(::Union{SV, Type{SV}}, ::Type{T})
# $(name){length(SV), T}
# end
# function StaticArrays.similar_type{SV <: $(name)}(::Union{SV, Type{SV}}, s::Tuple{Int})
# $(name){s[1], eltype(SV)}
# end
function StaticArrays.similar_type{SV <: $(name), T}(::Union{SV, Type{SV}}, ::Type{T}, s::Tuple{Int})
$(name){s[1], T}
end
function StaticArrays.similar_type{SV <: $(name)}(::Union{SV, Type{SV}}, s::Tuple{Int})
$(name){s[1], eltype(SV)}
end
function StaticArrays.similar_type{SV <: $(name), T}(::Union{SV, Type{SV}}, ::Type{T})
$(name){length(SV), T}

@generated function StaticArrays.similar_type{SV <: $(name), T,S}(::Type{SV}, ::Type{T}, s::Size{S})
if length(S) === 1
$(name){S[1], T}
else
StaticArrays.default_similar_type(T,s(),Val{length(S)})
end
end


eltype_or(::Type{$(name)}, or) = or
eltype_or{T}(::Type{$(name){TypeVar(:S), T}}, or) = T
eltype_or{S}(::Type{$(name){S, TypeVar(:T)}}, or) = or
Expand Down
10 changes: 4 additions & 6 deletions src/MArray.jl
Original file line number Diff line number Diff line change
Expand Up @@ -94,12 +94,10 @@ end
## MArray methods ##
####################

similar_type{M,T,N,L,S}(::Type{MArray{M,T,N,L}}, ::Type{S}) = MArray{M,S,N,L}

@pure size{Size}(::Type{MArray{Size}}) = Size
@pure size{Size,T}(::Type{MArray{Size,T}}) = Size
@pure size{Size,T,N}(::Type{MArray{Size,T,N}}) = Size
@pure size{Size,T,N,L}(::Type{MArray{Size,T,N,L}}) = Size
@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::Integer)
Base.@_inline_meta
Expand Down
9 changes: 3 additions & 6 deletions src/MMatrix.jl
Original file line number Diff line number Diff line change
Expand Up @@ -102,12 +102,9 @@ end
## MMatrix methods ##
#####################

similar_type{T,N,M,L,S}(::Type{MMatrix{N,M,T,L}}, ::Type{S}) = MMatrix{M,N,S,L}
similar_type{T,N,M,L,S}(::Type{MMatrix{N,M,T,L}}, ::Type{S}, Size::Tuple{Int}) = MVector{Size[1],S}

@pure size{S1,S2}(::Type{MMatrix{S1,S2}}) = (S1, S2)
@pure size{S1,S2,T}(::Type{MMatrix{S1,S2,T}}) = (S1, S2)
@pure size{S1,S2,T,L}(::Type{MMatrix{S1,S2,T,L}}) = (S1, S2)
@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::Integer)
#@boundscheck if i < 1 || i > length(m)
Expand Down
6 changes: 2 additions & 4 deletions src/MVector.jl
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,8 @@ end
## MVector methods ##
#####################

similar_type{T,N,S}(::Type{MVector{N,T}}, ::Type{S}) = MVector{N,S}

@pure size{S}(::Type{MVector{S}}) = (S, )
@pure size{S,T}(::Type{MVector{S,T}}) = (S,)
@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::Integer)
v.data[i]
Expand Down
10 changes: 4 additions & 6 deletions src/SArray.jl
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,10 @@ end
## SArray methods ##
####################

similar_type{M,T,N,L,S}(::Type{SArray{M,T,N,L}}, ::Type{S}) = SArray{M,S,N,L}

@pure size{Size}(::Type{SArray{Size}}) = Size
@pure size{Size,T}(::Type{SArray{Size,T}}) = Size
@pure size{Size,T,N}(::Type{SArray{Size,T,N}}) = Size
@pure size{Size,T,N,L}(::Type{SArray{Size,T,N,L}}) = Size
@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)

function getindex(v::SArray, i::Integer)
Base.@_inline_meta
Expand Down
9 changes: 3 additions & 6 deletions src/SMatrix.jl
Original file line number Diff line number Diff line change
Expand Up @@ -116,12 +116,9 @@ end
## SMatrix methods ##
#####################

similar_type{T,N,M,L,S}(::Type{SMatrix{N,M,T,L}}, ::Type{S}) = SMatrix{N, M, S, L}
similar_type{T,N,M,L,S}(::Type{SMatrix{N,M,T,L}}, ::Type{S}, Size::Tuple{Int}) = SVector{Size[1],S}

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

function getindex(v::SMatrix, i::Integer)
Base.@_inline_meta
Expand Down
6 changes: 2 additions & 4 deletions src/SVector.jl
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,8 @@ end
## SVector methods ##
#####################

similar_type{T,N,S}(::Type{SVector{N,T}}, ::Type{S}) = SVector{N,S}

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

@propagate_inbounds function getindex(v::SVector, i::Integer)
v.data[i]
Expand Down
6 changes: 2 additions & 4 deletions src/Scalar.jl
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,8 @@ end

@inline (::Type{Scalar{T}}){T}(x::Tuple{T}) = Scalar{T}(x[1])

similar_type{T,S}(::Type{Scalar{T}}, ::Type{S}) = Scalar{S}

@pure size(::Type{Scalar}) = ()
@pure size{T}(::Type{Scalar{T}}) = ()
@pure Size(::Type{Scalar}) = Size()
@pure Size{T}(::Type{Scalar{T}}) = Size()

getindex(v::Scalar) = v.data
@inline function getindex(v::Scalar, i::Int)
Expand Down
14 changes: 6 additions & 8 deletions src/SizedArray.jl
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,6 @@ end
@inline (::Type{SizedArray{S,T}}){S,T}(x::Tuple) = SizedArray{S,T,_dims(S),_dims(S)}(x)
@inline (::Type{SizedArray{S}}){S,T,L}(x::NTuple{L,T}) = SizedArray{S,T,_dims(S),_dims(S)}(x)

similar_type{S,T,N,M,R}(::Type{SizedArray{S,T,N,M}}, ::Type{R}) = SizedArray{S,R,N,M}

# Overide some problematic default behaviour
@inline convert{SA<:SizedArray}(::Type{SA}, sa::SizedArray) = SA(sa.data)
@inline convert{SA<:SizedArray}(::Type{SA}, sa::SA) = sa
Expand All @@ -65,23 +63,23 @@ similar_type{S,T,N,M,R}(::Type{SizedArray{S,T,N,M}}, ::Type{R}) = SizedArray{S,R

@pure _ndims{N}(::NTuple{N,Int}) = N

@pure size{S}(::Type{SizedArray{S}}) = S
@pure size{S,T}(::Type{SizedArray{S,T}}) = S
@pure size{S,T,N}(::Type{SizedArray{S,T,N}}) = S
@pure size{S,T,N,M}(::Type{SizedArray{S,T,N,M}}) = S
@pure Size{S}(::Type{SizedArray{S}}) = Size(S)
@pure Size{S,T}(::Type{SizedArray{S,T}}) = Size(S)
@pure Size{S,T,N}(::Type{SizedArray{S,T,N}}) = Size(S)
@pure Size{S,T,N,M}(::Type{SizedArray{S,T,N,M}}) = Size(S)

@propagate_inbounds getindex(a::SizedArray, i::Int) = getindex(a.data, i)
@propagate_inbounds setindex!(a::SizedArray, v, i::Int) = setindex!(a.data, v, i)

typealias SizedVector{S,T,M} SizedArray{S,T,1,M}
@pure size{S}(::Type{SizedVector{S}}) = S
@pure Size{S}(::Type{SizedVector{S}}) = Size(S)
@inline (::Type{SizedVector{S}}){S,T,M}(a::Array{T,M}) = SizedArray{S,T,1,M}(a)
@inline (::Type{SizedVector{S}}){S,T,L}(x::NTuple{L,T}) = SizedArray{S,T,1,1}(x)
@inline (::Type{Vector})(sa::SizedVector) = sa.data
@inline convert(::Type{Vector}, sa::SizedVector) = sa.data

typealias SizedMatrix{S,T,M} SizedArray{S,T,2,M}
@pure size{S}(::Type{SizedMatrix{S}}) = S
@pure Size{S}(::Type{SizedMatrix{S}}) = Size(S)
@inline (::Type{SizedMatrix{S}}){S,T,M}(a::Array{T,M}) = SizedArray{S,T,2,M}(a)
@inline (::Type{SizedMatrix{S}}){S,T,L}(x::NTuple{L,T}) = SizedArray{S,T,2,2}(x)
@inline (::Type{Matrix})(sa::SizedMatrix) = sa.data
Expand Down
15 changes: 2 additions & 13 deletions src/StaticArrays.jl
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ export Size
export @SVector, @SMatrix, @SArray
export @MVector, @MMatrix, @MArray

export similar_type, setindex
export similar_type
export push, pop, shift, unshift, insert, deleteat, setindex

include("util.jl")

Expand Down Expand Up @@ -55,17 +56,5 @@ if VERSION < v"0.6.0-dev.1671"
end
include("ImmutableArrays.jl")

# TODO list
# ---------
#
# * more tests
#
# * reshape() - accept Val? Currently uses `ReshapedArray`. Cool :)
#
# * permutedims() - accept Val? Or wait for `PermutedDimsArray` ?
#
# * Linear algebra - matrix functions (det, inv, eig, svd, qr, etc...)
# (currently, we use pointers to interact with LAPACK, etc)


end # module
Loading

0 comments on commit e6d7387

Please sign in to comment.