Providing {N} with type alias

Using StaticArrays and parametric types, I can successfully produce subtyped SVectors.

using StaticArrays

abstract type A{N,T} <: StaticVector{N,T} end
@inline Base.getindex(a::A, i::Int64) = (@boundscheck checkbounds(a,i); a.data[Base.to_index(i)])

struct B{T} <: A{3,T}
    data::NTuple{3,T}
end
julia> B(rand(3))
3-element B{Float64} with indices SOneTo(3):
 0.8678028298561054
 0.929557303378407
 0.22910477906211102

However, if I do something similar through a type alias, it doesn’t work as I had hoped.

struct C{N,T} <: A{N,T}
    data::NTuple{N,T}
end

const D{T} = C{3,T}
julia> D(rand(3))
ERROR: DimensionMismatch: No precise constructor for D found. Length of input was 3.
Stacktrace:
 [1] _no_precise_size(SA::Type, x::Tuple{Float64, Float64, Float64})
   @ StaticArrays C:\Users\jonda\.julia\packages\StaticArrays\dTwvg\src\convert.jl:161
 [2] construct_type(#unused#::Type{D}, x::StaticArrays.Args{Tuple{Tuple{Tuple{Float64, Float64, Float64}}}})
   @ StaticArrays C:\Users\jonda\.julia\packages\StaticArrays\dTwvg\src\convert.jl:81
 [3] StaticArray (repeats 2 times)
   @ C:\Users\jonda\.julia\packages\StaticArrays\dTwvg\src\convert.jl:165 [inlined]
 [4] convert
   @ C:\Users\jonda\.julia\packages\StaticArrays\dTwvg\src\convert.jl:204 [inlined]
 [5] (D)(a::Vector{Float64})
   @ StaticArrays C:\Users\jonda\.julia\packages\StaticArrays\dTwvg\src\convert.jl:174
 [6] top-level scope
   @ REPL[5]:1

Am I doing this wrong?

I don’t understand what you goal is nor would I call your approach a “type alias”. In both cases you define new types.

If your goal is to define a variant of StaticVector{N,T} with N fixed, then I would try const SV3 = StaticVector{3}.

Hmm ok, I assumed alias was the right term here following the docs at Types · The Julia Language. Also, because D is described as an alias of C, but I’m still learning:

julia> D
D (alias for C{3})

More specifically for what I’m trying to do, I have a class of vectors that all belong to A and have methods associated with A, and are all represented as SVectors. A can be broken into 2 subtypes (B and C) that still belong to A for its methods, but have their own operator overloads associated with them. B and C can further be broken down into subtypes (D,E…) that are the actual interface to the type graph (I would never create an A B or C, would just create D,E…). The trick is that while D is always length d, and E is always length e, d might not equal e. So I was hoping I could pass the size through parameters when I create D all the way up the type graph to A when it creates the SVector.

I could certainly do this by specifically defining the structs of A,B,C,D,E as I did with B above, but was wondering if aliases were appropriate here in a way that simplified the definitions in a Julian way. Something like:

using StaticArrays

struct A{N,T} <: StaticVector{N,T} 
    data::NTuple{N,T}
end
@inline Base.getindex(a::A, i::Int64) = (@boundscheck checkbounds(a,i); a.data[Base.to_index(i)])

const B{N,T} = A{N,T}
const C{N,T} = A{N,T}

const D{T} = B{3,T}
const E{T} = B{3,T}

const F{T} = C{3,T}
const G{T} = C{4,T}

Subtyping StaticArray here is making this more confused because it’s adding a different codepath on the abstract type. The crux is that Julia doesn’t automatically create a constructor that fills in some of the parameters:

julia> struct C{N,T}
           data::NTuple{N,T}
       end

julia> C((1,2,3))
C{3, Int64}((1, 2, 3))

julia> C{3}((1,2,3))
ERROR: MethodError: no method matching (C{3})(::Tuple{Int64, Int64, Int64})
Stacktrace:
 [1] top-level scope
   @ REPL[2]:1

julia> C{3,Int}((1,2,3))
C{3, Int64}((1, 2, 3))

You can define this constructor yourself:

julia> (::Type{C{N}})(d::NTuple{N,T}) where {N,T} = C{N,T}(d)

julia> C{3}((1,2,3))
C{3, Int64}((1, 2, 3))
3 Likes

So in the end I think I’ve gotten what I needed, and agree that I over complicated it. I’m subtyping StaticVector, but calling StaticVector directly actually produces the same error:

julia> StaticVector{6,Float64}(rand(6))
ERROR: DimensionMismatch: No precise constructor for StaticArray{Tuple{6}, Float64, 1} found. Length of input was 6.
Stacktrace:
 [1] _no_precise_size(SA::Type, x::NTuple{6, Float64})
   @ StaticArrays C:\Users\jonda\.julia\packages\StaticArrays\dTwvg\src\convert.jl:161
 [2] construct_type(#unused#::Type{StaticArray{Tuple{6}, Float64, 1}}, x::StaticArrays.Args{Tuple{Tuple{NTuple{6, Float64}}}})
   @ StaticArrays C:\Users\jonda\.julia\packages\StaticArrays\dTwvg\src\convert.jl:81
 [3] StaticArray (repeats 2 times)
   @ C:\Users\jonda\.julia\packages\StaticArrays\dTwvg\src\convert.jl:165 [inlined]
 [4] convert
   @ C:\Users\jonda\.julia\packages\StaticArrays\dTwvg\src\convert.jl:204 [inlined]
 [5] StaticArray{Tuple{6}, Float64, 1}(a::Vector{Float64})
   @ StaticArrays C:\Users\jonda\.julia\packages\StaticArrays\dTwvg\src\convert.jl:174
 [6] top-level scope
   @ REPL[11]:1

and so I have to define the struct with data::NTuple{X,T}, where I explicitly define X (unless I define a constructor as @mbauman recommended? more in a sec).

But my interface is typically not StaticVector{N,T}, typically we define SVector{N,T}. Changing the alias definition gets me I believe exactly what I want, and now that I’m reading back I think this is what @abraemer was recommending.

So now this works as I was hoping:

using StaticArrays

const A{N,T} = SVector{N,T}

const B{N,T} = A{N,T}
const C{N,T} = A{N,T}

const D{T} = B{3,T}
const E{T} = B{3,T}

const F{T} = C{3,T}
const G{T} = C{4,T}

f(d::D) = d.*d
f(g::G) = g + g
f2(a::A) = sqrt.(a)
julia> d = D([3,3,3])
3-element SVector{3, Int64} with indices SOneTo(3):
 3
 3
 3

julia> g = G([3,3,3,3])
4-element SVector{4, Int64} with indices SOneTo(4):
 3
 3
 3
 3

julia> f(d)
3-element SVector{3, Int64} with indices SOneTo(3):
 9
 9
 9

julia> f(g)
4-element SVector{4, Int64} with indices SOneTo(4):
 6
 6
 6
 6

julia> f2(d)
3-element SVector{3, Float64} with indices SOneTo(3):
 1.7320508075688772
 1.7320508075688772
 1.7320508075688772

julia> f2(g)
4-element SVector{4, Float64} with indices SOneTo(4):
 1.7320508075688772
 1.7320508075688772
 1.7320508075688772
 1.7320508075688772

But back on @mbauman response. I see what you’re saying about the partially filled parameters and needing to create our own constructors to cover missing parameters. I do still need the methods and behavior of SVectors on my types however, so I (maybe mistakenly?) assumed that subtyping was a must. Is there a way to maintain the behavior of SVectors in your example without subtyping?

In the end, had I wanted to use StaticVector{N,T} rather than SVector {N,T}, the constructor did solve my problem of “how do I pass N to the call for NTuple,” even while super typing.

struct H{N,T} <: StaticVector{N,T}
    data::NTuple{N,T}
end
@inline Base.getindex(A::H, i::Int64) = (@boundscheck checkbounds(A,i); A.data[Base.to_index(i)])
(::Type{H{N}})(d::NTuple{N,T}) where {N,T} = H{N,T}(d)

const H2 = H{3}
julia> H{3}(rand(3))
3-element H{3, Float64} with indices SOneTo(3):
 0.2669897586332304
 0.20288500609093174
 0.08817405671923229

julia> H2(rand(3))
3-element H2{Float64} with indices SOneTo(3):
 0.421901093230018
 0.6764301848959721
 0.3690068074848969

Thanks for the help! Trying to take the time to really understand these concepts.

1 Like

Why would you want to create your own type? What would it do differently from SVector? Just use SVector! :slight_smile:

If for example I wanted to make a Quaternion type that is always represented as (and hopefully gains the performance of SVectors. SVector{4} * SVector{4} is not defined, but I could define * on the Quaternion subtype to define quat multiplication.

Of course I could just create my own type

struct Quaternion
     val::SVector{4,Float64}
end

and define methods operating on val, or just define some function qmult, or just use Rotations.jl in this example, but wanted to explore aliasing for more succinctly getting the functionality I’m looking for in a text book like notation.

1 Like

Whoa, okay big misunderstanding for me. Going back to my example:

using StaticArrays

const A{N,T} = SVector{N,T}

const B{N,T} = A{N,T}
const C{N,T} = A{N,T}

const D{T} = B{3,T}
const E{T} = B{3,T}

const F{T} = C{3,T}
const G{T} = C{4,T}

f(d::T) where T<: D = T(d.*d)
julia> d = D([3,3,3])
3-element SVector{3, Int64} with indices SOneTo(3):
 3
 3
 3

julia> out = f(d)
3-element SVector{3, Int64} with indices SOneTo(3):
 9
 9
 9

julia> isa(out,D)
true

julia> isa(out,A)
true

julia> isa(out,F)
true

I’m glad out is a D and an A, but definitely don’t want it to be an F.

I think I’m officially convinced that aliasing doesn’t make sense for what I’m trying to do here, though I think I learned a lot about aliases! Thanks again for the help.

I’m not sure it’s been said explicitly, but “alias” in the printing you’ve shown earlier in the thread doesn’t mean “a new type that behaves just like this other type” (which is what I think haskell gives you with one of its type declarations?), but “this is egal to this other thing”. In other words, const Foo{N} = Bar{Int,N} means that Foo is not a new type, but exactly the same as if you’d write Bar{Int,N} (with appropriate values for N) everywhere.

1 Like