Extracting type parameters from type with many parameters

I’ve got a type with six type parameters, something like this:

struct MyType{A <: S, a0, a1, B <: T, b0, b1} end

My problem is that I want access to the type parameters in functions which take MyType values as arguments.
The best way of doing this that comes to mind is this:

my_a_type(::MyType{A, a0, a1, B, b0, b1}) where {A <: S, a0, a1, B <: T, b0, b1} = A
my_a0(::MyType{A, a0, a1, B, b0, b1}) where {A <: S, a0, a1, B <: T, b0, b1} = Val(a0)
my_a1(::MyType{A, a0, a1, B, b0, b1}) where {A <: S, a0, a1, B <: T, b0, b1} = Val(a1)
my_b_type(::MyType{A, a0, a1, B, b0, b1}) where {A <: S, a0, a1, B <: T, b0, b1} = B
my_b0(::MyType{A, a0, a1, B, b0, b1}) where {A <: S, a0, a1, B <: T, b0, b1} = Val(b0)
my_b1(::MyType{A, a0, a1, B, b0, b1}) where {A <: S, a0, a1, B <: T, b0, b1} = Val(b1)

But this is repetitive, especially as the parameter names would be longer in real code. Is there a better way, perhaps?

if you need to do this it feels like they should be field instead of type parameter?

1 Like

Given a concrete type Y, a way you can get its parameter values is Y.parameters. Wouldn’t work on MyType itself because it’s an abstract type where the parameters aren’t specified, but it’d work on MyType{Int, 1, :one, Float64, 3.5, 2}. .parameters is not part of the public interface, so don’t count on it still being there in future versions of Julia.

The full struct has fields, right? It’s more typical to compute with those fields or use typeof if you really need a field’s type. It’s very rare to get parameters directly from a type, and when it happens, a specific parameter is accessed by an isolated method like ndims (very similar to my_b_type) rather than some generic .parameters-based method. The idea is that if you’re treating something like data to compute on, it should usually be structured as instances/fields instead of type-annotations/parameters.

1 Like

Oh, I get it. A and B I can get via typeof on some already-existing struct fields; and for a0, a1, b0, b1 I just add some fields of type Val.

It’s not possible to say more on how you should organize the data without more context, but I would caution against that sort of information redundancy between fields and type parameters for a0, a1, b0, b1. It would mean storing the same type information in each and every instance, which can add up.

Is it at all possible that a0, a1, b0, b1 could be refactored entirely as fields instead of parameters for the type? Generally information is put in parameters because it should be known at compile-time, such as for dispatching to different methods or compiling more performant code. The downside is that there are more types to compile each method for. 6 parameters is a LOT; assuming there are only 2 values for each parameter, that’s 2^6 = 64 types right off the bat. If your ratio of concrete types to instances over repeated runs in practice approaches 1:1 instead of 0:1, your runtime improvements could easily be swamped by compile-time latency and compiled code bloat. I know that it’s not always obvious how much information should be put in types, but an example of a 1:1 ratio is struct CompileTimeInt{N} end.

When you say “add up” you seem to be implying that adding Val fields would make my structs heavier, however I think this can’t be true, because each Val type is a singleton type, so instances shouldn’t consume space.


julia> sizeof((Val(7), Val(3)))
1 Like

Oh that’s interesting, I never noticed that information in type parameters could trim a field’s memory usage to 0. I suppose the memory usage of instances with Val fields would scale with the number of unique Val types, not number of instances.

In the case of (Val(7), Val(3)), the Tuple type completely contains the information, so the instance doesn’t have to hold anything. But when a type, like an Array, can’t contain all the information, there is a memory cost; I don’t know the implementation detail exactly, but a Val array’s element looks pointer-sized.

Sizes of tuple or array before and after 1 additional Int8 element
julia> Base.summarysize( Int8.( (7,3) ) )
julia> Base.summarysize( Int8.( (7,3,1) ) )
julia> Base.summarysize( Val.(Int8.( (7,3) ) ) )
julia> Base.summarysize( Val.(Int8.( (7,3,1) ) ) )
julia> Base.summarysize( Int8.( [7,3] ) )
julia> Base.summarysize( Int8.( [7,3,1] ) )
julia> Base.summarysize( Val.(Int8.( [7,3] ) ) )
julia> Base.summarysize( Val.(Int8.( [7,3,1] ) ) )