Correct way to constrain the length of tuples in type definitions?


Suppose I want to define a type People that has two fields: names and ages. I would like names to hold a Tuple of String objects, and ages to hold a Tuple of Int objects. I would like both tuples to be of the same length.

I am currently parametrizing People and using the type parameter as the length of each Tuple.

struct People{N}
    names::NTuple{N, String}
    ages::NTuple{N, Int64}

Is this approach “correct”? I am asking because type parameters are supposed to define a family of types, and I am not exactly using the parameter for that purpose in this situation. Is there a nicer way of achieving the same result?



Looks alright to me. You may also want to use an array instead and check the fields are of the same length at runtime depending on what you will do with these structs.


I would just allow more general types, and use an inner constructor to validate, giving a nicer error than MethodError. Eg

using ArgCheck

struct People{S <: Tuple{Vararg{<:AbstractString}}, T <: Tuple{Vararg{<:Integer}}}
    function People(names::S, ages::T) where {S, T}
        @argcheck length(names) == length(ages)
        new{S, T}(names, ages)
julia> People(("A", "B"), (25, 30))
People{Tuple{String,String},Tuple{Int64,Int64}}(("A", "B"), (25, 30))

julia> People(("A", ), (25, 30))
ERROR: ArgumentError: length(names) == length(ages) must hold. Got
length(names) => 1
length(ages) => 2
 [1] macro expansion at /home/tamas/.julia/packages/ArgCheck/BUMkA/src/checks.jl:165 [inlined]
 [2] People(::Tuple{String}, ::Tuple{Int64,Int64}) at ./REPL[8]:5
 [3] top-level scope at none:0