Flattening Tuples of Types that form the parameter of a supertype

Dear all,

Below is an MWE of the code I’m writing.

I’m using the abstract supertype Pairer as a ‘tag’. Its two type parameters specify the ionic inputs and outputs to concrete subtypes.

Joiner and Extender make new concrete subtypes of Pairer out of old ones, in such a way that the ionic inputs are extended:

abstract type Ion end
struct Sodium <: Ion end
struct Potassium <: Ion end
struct Calcium <: Ion end
struct Proton <: Ion end

abstract type Pairer{Inputs,Outputs} end
## Inputs and Outputs are either Ions or tuples of Ions 

somehow_merge(Inputs, NewInputs) = Tuple{Inputs,NewInputs}

abstract type Extender{ExtraInput} end
## ExtraInput is either Ion or tuple of Ions 

struct Joiner{Input,ExtraInput,Output} <: Pairer{somehow_merge(Input, ExtraInput),Output}

struct HappyPairer <: Pairer{Sodium,Potassium} end
struct SadPairer <: Pairer{Tuple{Sodium,Proton,Calcium},Calcium} end
struct CalcExtender <: Extender{Calcium} end

show_inputs(p::Pairer{S,A}) where {S,A} = S

hap = HappyPairer()
sad = SadPairer()
c = CalcExtender()
j = Joiner(hap, c)
j2 = Joiner(sad, c)

show_inputs(j) gives: Tuple{Sodium, Calcium}. Great.

show_inputs(j2) gives Tuple{Tuple{Sodium, Proton, Calcium}, Calcium}. Bad.

What I want is for it to be Tuple{Sodium, Proton, Calcium}. In other words, flatten the nested tuple and take out unique elements.

But I haven’t been able to figure out a somehow_merge that can do this for tuples.

Now I should be able to pick through t1.parameters and t2.parameters where t1 and t2 are tuples of types, and then concatenate them to make a new tuple with all the elements of t1 and t2.

However, if I try modifying somehow_merge to do this, I get errors like

exception =
│    type TypeVar has no field parameters.

…except it clearly does!

What’s going wrong? Can anybody make a somehow_merge that gives

Tuple{Sodium, Proton, Calcium}

Thank you so much!

I’ve got an approximation that should work for nested tuples up to a certain size

typeflatten(::Type{T}) where {T} = (T,)
typeflatten(::Type{Tuple{T1}}) where {T1} = (typeflatten(T1)...,)
typeflatten(::Type{Tuple{T1, T2}}) where {T1, T2} = (typeflatten(T1)..., typeflatten(T2)...)
typeflatten(::Type{Tuple{T1, T2, T3}}) where {T1, T2, T3} = (typeflatten(T1)..., typeflatten(T2)..., typeflatten(T3)...)

@show Tuple{typeflatten(Tuple{Tuple{Int}, Int})...}
@show Tuple{typeflatten(Tuple{Tuple{Int, Int}, Int})...}
@show Tuple{typeflatten(Tuple{Int, Tuple{Int, Int}})...}
@show Tuple{typeflatten(Tuple{Tuple{Tuple{Int, Int, Int}}, Tuple{Tuple{Int, Int, Int}}, Tuple{Tuple{Int, Int, Int}}})...}


Tuple{typeflatten(Tuple{Tuple{Int}, Int})...} = Tuple{Int64, Int64}
Tuple{typeflatten(Tuple{Tuple{Int, Int}, Int})...} = Tuple{Int64, Int64, Int64}
Tuple{typeflatten(Tuple{Int, Tuple{Int, Int}})...} = Tuple{Int64, Int64, Int64}
Tuple{typeflatten(Tuple{Tuple{Tuple{Int, Int, Int}}, Tuple{Tuple{Int, Int, Int}}, Tuple{Tuple{Int, Int, Int}}})...} = NTuple{9, Int64}

Would be interesting to know if there is a better solution.

Edit: to apply this to your problem you would use

show_inputs(p::Pairer{S,A}) where {S,A} = Tuple{typeflatten(S)...}
1 Like

thanks, that’s a nice approximate solution!

for my use case, i decided to allow nested tuples, and access their elements through a recursive algorithm like so:

function testsensed(s::DataType) # takes in potentially nested tuple
    map(fieldtypes(s)) do el
        el <: Tuple && return testsensed(el)
        return (el,)
    end |> Iterators.flatten |> collect |> unique!

But i’d also be interested, from a learning julia point of view, to see any alternatives that directly solve the issue