# Get tuple length from type?

Is there a built-in function to get the length of a tuple as a `Val{N}`?

Basically I’m looking for something that would be of the form:

``````tuplen(::Type{Tuple{}}) = Val{0}()
tuplen(::Type{Tuple{T}}) where T = Val{1}()
tuplen(::Type{Tuple{T1, T2}}) where {T1, T2} = Val{2}()
tuplen(::Type{Tuple{T1, T2, T3}}) where {T1, T2, T3} = Val{3}()
...
``````

In case this is an XY problem - what I’m actually trying to do is convert an array-of-tuples representation to a tuple-of-arrays representation, and with the above definition I can do:

``````"""
Convert an Array-of-Tuples to a Tuple-of-Arrays.
"""
toa(aot) = ntuple(i->getindex.(aot, i), tuplen(eltype(aot)))
``````

without the above I could use `length(first(aot))` to get the length but that assumes the array is not empty.

edit: as a nested XY problem, I just realized that Plots can take vectors of tuples natively and I don’t need to transform my data to tuple-of-vectors after all. Carry on…

2 Likes

Yup–it’s actually a simple one-liner:

``````julia> tuple_len(::NTuple{N, Any}) where {N} = Val{N}()
tuple_len (generic function with 1 method)

julia> tuple_len((1, 2, 3))
Val{3}()

julia> tuple_len((1, "hello", π))
Val{3}()

julia> tuple_len((1, "hello", π, [1]))
Val{4}()
``````

This works because tuples (unlike all other types) are variadic, so you can dispatch on `NTuple{Any, N}` to match all tuples of length N.

10 Likes

Ah, thanks for that. I’d forgotten that tuple types are special.

2 Likes

So why isn’t it `length()`? Already defined in Base?

Sorry, that isn’t the same thing as I just realized. `length()` already exists in base for tuples,
but returns an integer for the length.

Also it’s not defined on the `Tuple` type, only tuple values:

``````julia> length(Tuple{Float64, Int64})
ERROR: MethodError: no method matching length(::Type{Tuple{Float64,Int64}})
Closest candidates are:
length(::Core.SimpleVector) at essentials.jl:593
``````

Right.

It’s also worth noting that the compiler is pretty good at dealing with tuple lengths as regular old integers using the built-in length function. For example, we can write a function that looks like it might be type-unstable:

``````julia> function maybe_type_unstable(x::Tuple)
if length(x) == 2
return 1.0
else
return "hello world"
end
end
maybe_type_unstable (generic function with 1 method)
``````

but the compiler is smart enough to figure out the value of `length(x)` at compile time and infer the result:

``````julia> @code_warntype maybe_type_unstable((1, 2))
Variables
#self#::Core.Compiler.Const(maybe_type_unstable, false)
x::Tuple{Int64,Int64}

Body::Float64
...
``````
``````julia> @code_warntype maybe_type_unstable((1, 2, 3))
Variables
#self#::Core.Compiler.Const(maybe_type_unstable, false)
x::Tuple{Int64,Int64,Int64}

Body::String
...
``````
6 Likes

Sorry to came so late after the fight, but we finally don’t have a solution to get the length of a tuple type ?

Do you mean besides defining the one-liner function marked as a solution?

Yep this function gets the length of a tuple, not from the tuple type ad was requested at the beginning (and as it seems the OP is referenced) . Actually I found a solution asking another question there How can i index a tuple type? so nvm

Type{Tuple{…}} Is not the same as NTuple{…}

The solution in the other thread may be efficient, but is probably an implementation detail that cannot be relied upon.

As it is said in the accepted answer:

So you can use `NTuple{N, T}` to match any `Tuple{...}`. The only matter is that you want the function to take tuple types instead of tuples. The solution below is correct and do not rely in any implementation details, while it may be ugly and inefficient.

``````julia> tuple_type_length(x) = (n = -1; while !(x <: NTuple{n+=1, Any}); end; return n)

tuple_type_length (generic function with 1 method)

julia> tuple_type_length(Tuple{Int, Char})
2

julia> tuple_type_length(Tuple{Int, Char, String})
3

``````
1 Like

One can avoid internals by using

``````julia> fieldcount(Tuple{Int,Float64})
2
``````
2 Likes

Seems a good solution, I did not know `fieldcount`. My only worry is the caveat in the documentation:

Get the number of fields that an instance of the given type would have. An error is thrown if the type is too abstract to determine this.

Kinda hard to know beforehand what is a “too abstract” type.

Any concrete type should be fine with `fieldtype`. Even many incomplete types with defined-but-type-unspecified fields such as `Complex{T} where T`, `Tuple{T,T} where T`, and `Tuple{Tuple{Vararg}}` will work.

Examples of types that will fail are `Tuple` (with no further elaboration), `Tuple{Vararg}`, and `NTuple{N,Any} where N`. The number of fields that these types have is undefined.

So `fieldcount` should work with almost any type that actually has a number of fields. One might be able to cook up some pathological corner cases, but I haven’t thought of one yet. If you find one that fails that you think shouldn’t, you can consider opening a bug report.

2 Likes

Is there anything wrong with this variant of the accepted solution (sligthly adapted to work on Tuple types instead of Tuple instances)?

``````julia> tuple_len(::Type{<:NTuple{N, Any}}) where {N} = Val{N}()
tuple_len (generic function with 1 method)

julia> x = (1, "hello", 42.0)
(1, "hello", 42.0)

julia> tuple_len(typeof(x))
Val{3}()
``````
2 Likes

I believe there is no problem, but I would adopt the simpler `fieldcount` solution now that I am aware of it, unless you want the code to fail in non-tuple types.

OK, but `fieldcount` gives a plain integer result, as opposed to the initial requirement that the result be a `Val`-wrapped value.

I agree that, since constant propagation is likely to work well in many cases, `fieldcount` would probably a very good and standard way to do this (but in those cases, the question then becomes: why not simply `length`?)

Is there any api guarantee that an NTuple will always have exactly N fields? Using `fieldcount` seems a bit ‘hacky’.

1 Like

`length` works if you have an instance of the object, true, but I thought the idea was to have a function that would work directly over the type?

Also, yes, `fieldtype` does not return a `Val` but neither does `N` in the parameter type annotation, both give `Int` values which can after be wrapped inside `Val`. It may be that one solution is recognized as type-stable by the compiler (i.e., recognizes the static mapping between input and output types) and the other solution isn’t, but this is speculation and should be checked.

1 Like