How to create struct where type parameter is a parametric type itself?

I’m simplifying my code here, but the problem is like this:

struct MyType{Array{T<:Real}}
    field::T
    function MyType(x::Array{T}) where T<:Real
        new{Array{T}}(sum(x))
    end
end

A workaround I found is to write struct MyType{T<:Real, S<:Array{T}} instead. Is this the only way to go about this? Why doesn’t the initial syntax work?

1 Like

In the real example, do you care about the Array or do you only care about its type parameter T? If you only care about T, you could do something like

struct MyType{T<:Real}
    field::T
    function MyType(x::Array{T}) where T<:Real
        new{T}(sum(x))
    end
end

I actually do care about the parametric type (which isn’t Array in the real example). It’s a series of other composite types I made and I’d like MyType to be able to take them as a parameter.

The pattern to achieve this is usually to have additional “redundant” type parameters. An example of such a type is SubArray, which is produced by view:

julia> typeof(view(zeros(5,5),1:3,3))
SubArray{Float64, 1, Matrix{Float64}, Tuple{UnitRange{Int64}, Int64}, true}

The first parameter is the eltype, the second is the dimension of the view, the third is the parent array type (which includes the eltype as a sub-parameter, in this case), the fourth is the type of the axis slices, and the fifth flags whether the view is strided.

Note that the parent type and the slice types are sufficient to deduce the other three parameters. Nevertheless, they are added as parameters because they are useful for dispatching other functions.

So in your case, you might parameterize your struct like this

julia> struct MyType{T, A<:AbstractArray{T}}
           field::T
           function MyType(x::A) where {T<:Real, A<:AbstractArray{T}}
               new{T,A}(sum(x))
           end
       end

julia> MyType(rand(10))
MyType{Float64, Vector{Float64}}(5.716220410885628)
3 Likes

Thanks this makes sense. I always wondered why I kept seeing this redundant pattern in places. So the idea is there where doesn’t accept nested parametrized types, so you have to declare the inner parameter seperately and then it’s fine? Could your code be struct MyType{T, Array{T}} instead?

Not as far as I’m aware. You can only have parameters declared there, plus any type bounds you want to impose on them. But you could write struct MyType{T, A<:Array{T}}.

Honestly, the type bounds there aren’t even really important and I seldom impose them. They only exist to throw errors when you try to construct invalid instances, but don’t usually improve performance or have other benefits. Maybe there are small benefits with some recent changes to abstract inlining when there are already existing type instabilities? But I haven’t experimented and it’s seldom relevant to well-written code.

Also, since you keep writing it, I’ll point out that Array{T} is not a concrete type so no instance of one can exist. You can still write (most) code with it, but it may produce type instabilities.

julia> (Array{T} where T) == (Array{T,D} where {T,D})
true

You probably mean to write Array{T,1} a.k.a. Vector{T}. Or perhaps you really do want the dimension parameter to be free, in which case you definitely need A<:Array{T}. But I don’t see why 1:5, which isa UnitRange{Int}, shouldn’t be a valid input, which is why I suggested the more permissive A<:AbstractArray{T}.

1 Like

That part is the where clause of the struct. where {T, Array{T}} is more obviously not valid; you need the S<:Array{T} so S could annotate something (though nothing in this case). Your working struct probably looks like this:

struct MyType{T<:Real, S<:Array{T}}
  field::T
  function MyType(x::Array{T}) where T<:Real
    new{T,typeof(x)}(sum(x))
  end
end

A weird alternative is to store the array type in a field instead, and you wouldn’t even need to pass a full array instance sometimes. The type field does take up extra space though. It’s also possible to parameterize this by the type, but it does make the array type field abstractly annotated.

struct MyType2{T<:Real}
  field::T
  arraytype::DataType
  function MyType2(x::Array{T}) where T<:Real
    new{T}(sum(x), typeof(x))
  end
end
1 Like