Specifying structs with variably nested NTuple fields

I suspect what I want isn’t possible but wanted to ask here to be certain (or to learn precisely why it isn’t possible).

The setup for the question is as follows. Suppose I have a function:

function nested_ntuple(::Val{Ns}, ::Type{T}) where {Ns, T}
   if length(Ns) == 1
      return NTuple{first(Ns), T}
   else
      return NTuple{first(Ns), nested_ntuple(Val{Base.tail(Ns)}(), T)}
   end
end

which generates nested NTuple types, such that e.g.

nested_ntuple(Val{(1,2)}(), String)    === NTuple{1, NTuple{2, String}}
nested_ntuple(Val{(2,4,3)}(), Float64) === NTuple{2, NTuple{4, NTuple{3, Float64}}}

and so on.

What I’m then interested in is specifying a type with variably nested NTuple fields. I.e., conceptually, I’m looking for something like this:

struct A{Ns, T}
   x::nested_ntuple(Ns, T)
end

(Of course, this isn’t a valid signature for specifying a type since we cannot use functions on the type parameters for specifying struct fields - but the point is just to say that it should assign a nested tuple type to x).

As context, the reason I am interested in this was to play around with alternatives to e.g. StaticArrays’ use of one long “flat” NTuple for all array data - and the associated “dangling” type parameter L - i.e., I wanted to see what the implications are of instead using a nested NTuple approach (in the spirit of Proposal: Defer calculation of field types until type parameters are known · Issue #18466 · JuliaLang/julia · GitHub - and motivated by that entire issue as well).

You could go the easy way:

julia> struct A{T}
         x::T
       end

julia> A(nested_ntuple(Val{(1,2)}(), String))
A{DataType}(Tuple{Tuple{String, String}})

julia> A((1,(1,2)))
A{Tuple{Int64, Tuple{Int64, Int64}}}((1, (1, 2)))

(your function is returning a data type, I’m not sure if that is what is originaly intended).

You could also add type parameters Ns and T to A only for dispatch:

julia> struct AA{Ns,T,Tup}
         x::Tup
       end

julia> AA{Ns,T}(tup) where {Ns,T} = AA{Ns,T,typeof(tup)}(tup)

julia> AA{2,Int}((1,(1,2)))
AA{2, Int64, Tuple{Int64, Tuple{Int64, Int64}}}((1, (1, 2)))


1 Like

Hmm, yeah, that is an option, true - hadn’t thought of that - albeit not a very pleasant one :(.
One of my hopes was to see if it would be possible to simplify the type signature of e.g. SArray{Size, T, D, L} to SArray{Size, T, D}, i.e. to get rid of extraneous type parameters - which this sort of acts in opposition to.

It was intentional that the nested_ntuple is returning a type in this case; otherwise the pseudocode-example for struct A ... wouldn’t make sense. Of course, to give the values of A.x we’d use an instance of that type.

Generally you cannot do computations like this on types in a struct definition.

The common approach is to leave the type of x parametric, and validate in the inner constructor, eg

using ArgCheck

struct A{Ns,T,S}
    x::S
    function A{Ns,T}(x::S) where {Ns,T,S}
        @argcheck is_nested_ntuple(Val(Ns), Val(T), x) # implement this
        new{Ns,T,S}(x)
    end
end

I’m not sure I understand? - this was exactly what I wrote in the bit of text you left out from that quote:

And also the whole topic of the issue #18466 that I linked to?


Did you see my previous reply to @lmiq equivalent suggestion? The suggestion is nice, definitely, just not what I was aiming for with this. (Though, I wasn’t aware of ArgCheck; that’s a nice alternative to writing out constructor validations manually - thanks)


A different way of phrasing my question is whether there’s a non-functional way of expressing a nested NTuple type (i.e., in the same sense that NTuple{N, T}/Tuple{Vararg{T, N}} allows a type-declaration of a definite-length Tuple{T, T, ...}).
It seems there isn’t? - but it’d be cool to learn more about why that is.