Using Vararg for mixed types

Recently I have started getting the error message:

WARNING: Wrapping Vararg directly in UnionAll is deprecated (wrap the tuple instead).
│ You may need to write f(x::Vararg{T}) rather than f(x::Vararg{<:T}) or f(x::Vararg{T}) where T instead of f(x::Vararg{T} where T).

My problem is I don’t think it is possible for me to dispatch the way I would like with the suggested change.

To give a toy example:
I want to write a function that accepts all of the following tuples:

(Int(1), ) 
(Int(1), Int(2), ) 
(Int(1), Int32(2), )
(UInt64(1), UInt32(2), )

i.e. any combination of Integers of any length. To accomplish this I use Vararg{<:Integer}, which works. Using Vararg{Integer} does not work in case 3 or 4 nor does Vararge{I} where I<:Integer (which is semantically identical?), only work when all the Integers are the same concrete type, i.e. only matching case 1 and 2.

Why is this being deprecated, and how can I retain the functionality I’m using currently?

Dispatch works for me, what are you calling exactly?

julia> foo(::Vararg{Integer}) = nothing;

julia> foo(Int(1),)

julia> foo(Int(1), Int(2),)

julia> foo(Int(1), Int32(2),)

julia> foo(UInt64(1), UInt32(2),)

Huh… maybe I just did something stupid. Thanks for the sanity check. I’ll give it another go and come back with a concrete example if I still can’t fix it.

I think the confusion here relates to the covariance/contravariance exception that Julia has made for Tuple that makes it different from most other types:

julia> Vector{Int64} <: Vector{Integer} # most types
false

julia> Tuple{Int64} <: Tuple{Integer} # exception for Tuple
true

julia> Tuple{Int64,UInt8} <: Tuple{Vararg{Integer}} # extends to Vararg
true

Vararg is intimately related to Tuple, although I can’t speak to the precise details of the relationship. But in any case, Vararg{Integer} manages to match any number of arguments whose types are <:Integer for the same reason that Tuple has this behavior.

Thank you for the explanation. I was aware that tuples are special, as they are how function arguments are represented. Therefore as f(::Int64, ::Int32) matches f(::Integer, ::Integer) it makes sense that Vararg might behave similarly.

Right on the money, Vararg is just the parameter for trailing element types of a tuple type, so they are covariant like any other tuple type parameter, not invariant like all other type parameters. In the case of methods, the tuple type is the argument types tuple. For example, foo(x::Complex, args::Integer...) has the (abstract) type Tuple{Complex, Vararg{Integer}}. The instances correspond to the method’s possible inputs, like (3+1im, 1, Int32(2))::Tuple{Complex{Int}, Int, Int32}.

To be more accurate, we’d include the type of the callable too, since that is also dispatched on and is how callable instances work.

2 Likes