Clarification on usage for non-type type parameters

Hi, I was trying to define an abstract type which I would like to be subtype of FieldVector{N,T}, from the package StaticArrays, where N must be Unsigned. If it were simply a case like the following

abstract type MyType{N, T} <: FieldVector{N, T} end

everything is ok, it precompiles an executes beautifully. The problem arises when I do want to manipulate N before passing it to FieldVector. For example if I were to try and write

abstract type MyType{N, T} <: FieldVector{2N, T} end

the resulting error would state

ERROR: MethodError: no method matching *(::Int64, ::TypeVar)
Closest candidates are:
  *(::Any, ::Any, ::Any, ::Any...) at operators.jl:560
  *(::T, ::T) where T<:Union{Int128, Int16, Int32, Int64, Int8, UInt128, UInt16, UInt32, UInt64, UInt8} at int.jl:88
  *(::Union{Int16, Int32, Int64, Int8}, ::BigInt) at gmp.jl:541
  ...
Stacktrace:
 [1] top-level scope
   @ REPL[4]:1

which clearly indicates that, by default, any type parameter is interpretered as a TypeVar instance which, obviously, has no multiplication operation defined. So I try and specify to the compiler that N is of type Unsigned using the same syntax I use for the functions

abstract type MyType{N::Unsigned, T} <: FieldVector{2N, T} end

but this raises the cryptic error

ERROR: syntax: invalid variable expression in "where" around REPL[5]:1
Stacktrace:
 [1] top-level scope
   @ REPL[5]:1

So my question is: how do I tell the compiler that N is a non-type type parameter?

I tried to look for examples of this behavior online and in the source code of some libraries but I found no solution in my search.

Could you explain in which situation you would do that?

this wouldn’t change anything for type-inference except the N of the custom type being another number than that of the FieldVector, but if the number of fields is smaller that subtyping would break everything that was implemented for FieldVector:

julia> struct V{N,T} <: FieldVector{N,T}
         x
         y
       end

julia> x = ones(V{3,Float64})
ERROR: StackOverflowError:

So maybe if you explain what exactly what you want to achieve with that subtyping a better solution appears.

There’s currently no way to do this. The typical approach is to introduce a new type parameter and then enforce the invariant in your inner constructor:

struct MyType{N, T, N2}
  ...
  function MyType{N, T}(...)
    new{N, T, 2 * N}(...)
  end

  # or, alternatively
  function MyType{N, T, N2}(...)
    @assert N2 == 2 * N
    ...
  end
end

GitHub - vtjnash/ComputedFieldTypes.jl: Build types in Julia where some fields have computed types can help automate some of this for you. Unfortunately, abstract types do not have inner constructors, so I’m not sure how to apply this suggestion to the abstract MyType, except to pass the work of checking N2 on to whatever concrete types end up inheriting from MyType.

2 Likes

This is unfortunate, your solution is interesting, I’ll try and implement it, but delegating the work of an abstraction to its concrete types removes the elegancy of the usual notation. I hope a language level workaround will be found in future versions of Julia. Thanks!

1 Like

Yeah, I agree it’s pretty awkward.

You could consider changing your abstraction such that the N in MyType is equal to the N in the FieldVector (twice your current value). Or perhaps you can treat your data as a pair of N-element vectors instead of a single 2N element vector. Or maybe there’s some other more convenient way to do whatever it is that you’re actually hoping to accomplish.

1 Like

For the particular case of FieldVector from StaticArrays, what that could be useful for? (sincere doubt, maybe I am missing something). FieldVector is thought to provide a convenient way to define, for example:

struct Vec{3,T} <: FieldVector{3,T}
  x::T 
  y::T 
  z::T 
end

such that all algebra operations and methods that are defined for static vectors will work for Vec. If one defines Vec with a different inner structure than that (3 fields of the same number type) all those methods will break. Is there any other utility for defining a subtype that does not follow the strict pattern of FieldVector{N,T}?

The case I presented in the original post was greatly simplified to not divert attention from the core issue.

What I am actually trying to accomplish is a basic implementation of Geometric Algebra, in particular of the multivector.

A multivector in N dimensions has sum(binomial(N, k) for k ∈ 0:N) values associated (in 3D they are 8: a scalar, three vector components, three pseudovector components and a pseudoscalar).

Since i could not find a way to declare a MultiVector{N, T} type on the fly at runtime (I might ask another question for that), I thought to manually implement just the N = 1, 2, 3 cases since they are the most used. An example in 3D would be

struct MultiVector3D{T} <: MultiVector{3, T}
    # scalar
    s::T 
    # vector components 
    x̂::T
    ŷ::T
    ẑ::T
    # pseudovector components
    x̂ŷ::T
    x̂ẑ::T
    ŷẑ::T
    # pseudoscalar
    x̂ŷẑ::T
end

These concrete types would be subtypes of an abstract type defined as

nfields(n::Unsigned) = sum(binomial(n, k) for k ∈ 0:n)
abstract type MultiVector{N, T} <: FieldVector{nfields(N), T}

so that common behavior could be defined once and the code is cleaner and more organized.

I don’t know if maybe there is a lampant workaround to my specific problem that I haven’t thought about.