We know that for UnionAll of Tuple that uses the same type parameter (e.g., T) for all the element types (in default) requires the element types to be the same concrete type:
julia> f1(a::Tuple{T, T}) where {T<:Real} = a
f1 (generic function with 1 method)
julia> f1((1, 1))
(1, 1)
julia> f1((1, 1.0))
ERROR: MethodError: no method matching f1(::Tuple{Int64, Float64})
The function `f1` exists, but no method is defined for this combination of argument types.
I was hoping that if I replace T with a finite Union of types parameterized by T, the requirement of T to be the same concrete type still holds:
julia> f2(a::Tuple{Union{T, Complex{T}}, Union{T, Complex{T}}}) where {T<:Real} = a
f2 (generic function with 1 method)
julia> f2((1, 1.0)) # Expect this to prompt `MethodError` assuming `T` iterate through all possible concrete types
But that’s not the case:
julia> f2((1, 1.0)) # This actually is legal as `T==Real` somehow is allowed
(1, 1.0)
Is this consistent with the design of UnionAll?
If so, how can I parameterize the type signature of f2 to allow each element of a to be either T or Complex{T}, but must have the same concrete T? In other words, any (T1, T2) in Tuple{Union{T1, Complex{T1}}, Union{T2, Complex{T2}}} such that T1!==T2 || !isconcretetype(T1) should be disallowed.
Thanks!