I’m not as deep into Julia specifics, just a naive answer. I think people would be confused if this doesn’t work anymore:
x = [1, "2"]
push!(x, '3')
Even more, the Union trick would probably only make sense if the number of different types is limited, so it would be case specific which type this constructor yields.
It’s not clear if [1, "2"] would necessarily be a Vector{Any}, although it turns out to be the case. For example, the following doesn’t work, as it does produce a union as the eltype:
julia> x = [1, nothing];
julia> push!(x, missing)
ERROR: MethodError: Cannot `convert` an object of type Missing to an object of type Int64
julia> typeof(x)
Vector{Union{Nothing, Int64}} (alias for Array{Union{Nothing, Int64}, 1})
Even more, the Union trick would probably only make sense if the number of different types is limited
Yes, I agree, this may only be chosen as the return type for a few arguments. This is type-stable, albeit somewhat perplexing.
My question is: since this already is adopted in the special cases of nothing and missing, why not make this more universal, if only for singleton types? I had seen an issue recently where there was some discussion on nothing and missing being special, which is a bit unfortunate.
One challenge with doing what you’ve specifically asked is that I think it’s reasonable to want [1, 2.5] to promote both entries to Float64 rather than make a Vector{Union{Int,Float64}}. The distinction here is that promote(1,2.5) “succeeds” whereas promote(A(),B()) for your example does not.
So it seems that the preferred behavior would be to try to attempt to promote the elements first, then introduce a Union if multiple (or maybe only a few) distinct types remain.
In the meantime, perhaps this function definition will be useful to you?
unionvec(x...) = Union{typeof.(x)...}[x...]
It seems to do what you asked, although doesn’t attempt to do the promotion that I had suggested nor does it resort to Any beyond a certain number of distinct types.