How to strip tuple type of the element types

I want a function taking a type T<:Tuple type and returning S<:Tuple type such that T<:S and the element types are stripped from the input, but with preserved length information.

Desired behavior examples:

f(Tuple{}) == Tuple{}
f(Tuple{Int,String}) == f(Tuple{X,X} where {X<:Number}) == Tuple{Any,Any}
f(Tuple{Int,Vararg{String}}) == Tuple{Any,Vararg}
f(Union{Tuple{Int},Tuple{String}}) == Tuple{Any}

Any ideas for implementing this?

Here’s an approximation:

const Tuple1OrMore = Tuple{Any,                Vararg}
const Tuple2OrMore = Tuple{Any,Any,            Vararg}
const Tuple3OrMore = Tuple{Any,Any,Any,        Vararg}
const Tuple4OrMore = Tuple{Any,Any,Any,Any,    Vararg}
const Tuple5OrMore = Tuple{Any,Any,Any,Any,Any,Vararg}

g(::Type{<:Tuple       }) = Tuple
g(::Type{<:Tuple1OrMore}) = Tuple1OrMore
g(::Type{<:Tuple2OrMore}) = Tuple2OrMore
g(::Type{<:Tuple3OrMore}) = Tuple3OrMore
g(::Type{<:Tuple4OrMore}) = Tuple4OrMore
g(::Type{<:Tuple5OrMore}) = Tuple5OrMore

f(::Type{T}) where {len, T<:NTuple{len,Any}} = NTuple{len,Any}
f(::Type{T}) where {T<:Tuple} = g(T)
julia> f(Tuple{})
Tuple{}

julia> f(Tuple{Int,String})
Tuple{Any, Any}

julia> f(Tuple{Int,Vararg{String}})
Tuple{Any, Vararg}

julia> f(Union{Tuple{Int},Tuple{String}})
Tuple{Any}

For fixed-length tuple type without Vararg, f(T) = NTuple{fieldcount(T)} or f(T) = NTuple{fieldcount(T), Any} should work – depending on what you need exactly.

The fixed-length case is already accurate above:

So I’m specifically interested in the Vararg case.

Well,

g(::Type) = Any
g(::typeof(Vararg)) = Vararg
f(T) = Tuple{map(g, T.parameters)...}

should work for all tuple types, not for Unions though.

1 Like

My solution.

concatenate_tuple_type(::Type{A},::Type{B}) where {A<:Tuple, B<:Tuple} = Tuple{A.parameters...,B.parameters...}
test_vararg(::typeof(Vararg)) = true
test_vararg(x::Type) = false
test_union(x::Union) = true
test_union(x::Type) = false

f(::Type{Tuple{}}) = Tuple{}
f(::Type{A}) where {A<:Tuple} = test_union(A) ? Union{f(A.a),f(A.b)} : (test_vararg(A.parameters[end]) ? concatenate_tuple_type(NTuple{length(A.parameters)-1,Any},Tuple{Vararg}) : NTuple{length(A.parameters),Any})
println(Tuple{Int,Int}.parameters)
1 Like

Interesting, however I’ll need to adapt it further to make it work for UnionAll:

julia> Tuple{X,X} where {X<:Number}
Tuple{X, X} where X<:Number

julia> f(ans)
ERROR: type UnionAll has no field parameters

Also, I’ll probably replace the predicates with traits, I think that would help prevent run time dispatch for long tuple types/deep recursion.

A solution, it’s quite nice and doesn’t use implementation details, like the parameters field:

h(::V) where {V<:Val} = Tuple{ntuple(Returns(Any), V())..., Vararg}

function g(::Type{T}, ::Val{n}) where {T<:Tuple,n}
  if T <: h(Val(n))
    g(T, Val(n + 1))::Type{<:Tuple}
  else
    h(Val(n - 1))
  end
end

f(::Type{T}) where {T<:Tuple} = g(T, Val(1))::Type{<:Tuple}

f(::Type{T}) where {len,T<:NTuple{len,Any}} = NTuple{len,Any}

A variant:

h(::V) where {V<:Val} = Tuple{ntuple(Returns(Any), V())..., Vararg}

function g(::Type{T}, ::Val{n}) where {T<:Tuple,n}
  if T <: h(Val(n))
    g(T, Val(n + 1))::Val
  else
    Val(n - 1)
  end
end

f(::Type{T}) where {T<:Tuple} = h(g(T, Val(1)))::Type{<:Tuple}

f(::Type{T}) where {len,T<:NTuple{len,Any}} = NTuple{len,Any}

With branching replaced by dispatch:

h(::V) where {V<:Val} = Tuple{ntuple(Returns(Any), V())..., Vararg}

g(::Type{T}, ::Val{n}, ::Type{S}) where {T<:Tuple,n,S<:Tuple} = Val(n - 1)
function g(::Type{T}, ::Val{n}, ::Type{S}) where {n,S<:Tuple,T<:S}
  v = Val(n + 1)
  g(T, v, h(v))::Val
end

function f(::Type{T}) where {T<:Tuple}
  v = Val(1)
  h(g(T, v, h(v)))::Type{<:Tuple}
end

f(::Type{T}) where {len,T<:NTuple{len,Any}} = NTuple{len,Any}

With ntuple replaced by more recursion:

ntuple_any(::Val{0}) = ()
function ntuple_any(::Val{n}) where {n}
    (n::Int ≤ 0) && error("unexpected")
    m = n - 1
    t = ntuple_any(Val(m))::NTuple{m,DataType}
    (Any, t...)::NTuple{n,DataType}
end
h(::V) where {V<:Val} = Tuple{ntuple_any(V())..., Vararg}

g(::Type{T}, ::Val{n}, ::Type{S}) where {T<:Tuple,n,S<:Tuple} = Val(n - 1)
function g(::Type{T}, ::Val{n}, ::Type{S}) where {n,S<:Tuple,T<:S}
  v = Val(n + 1)
  g(T, v, h(v))::Val
end

function f(::Type{T}) where {T<:Tuple}
  v = Val(1)
  h(g(T, v, h(v)))::Type{<:Tuple}
end

f(::Type{T}) where {len,T<:NTuple{len,Any}} = NTuple{len,Any}