Types, strange behavior

Julia Version 1.5.3 (2020-11-09)

Hi, looking at this

vector=[[1,2],[3,[4,5]]]
vector[1]
typeof(vector[1])


julia> vector=[[1,2],[3,[4,5]]]
2-element Array{Array{Any,1},1}:
 [1, 2]
 [3, [4, 5]]
julia> vector[1]
2-element Array{Any,1}:
 1
 2
julia> typeof(vector[1])
Array{Any,1}

Is there a good reason Julia cannot define the type to be Vector{Int64}?

Julia prefers to give a specific type to the outer container: vector is an Array{Array{Any,1},1} === Vector{Vector{Any}} rather than Vector{Any}. So the type of each element is Vector{Any}. And a Vector{Int64} is not a subtype of Vectory{Any} so it cannot store [1,2] as Vector{Int64}.

If you prevent Julia from using a specific type for the outer container, it works as you want:

julia> vector = [[1,2], [3,[4,5]], 1]
3-element Array{Any,1}:
  [1, 2]
  Any[3, [4, 5]]
 1

julia> typeof(vector[1])
Array{Int64,1}

or

julia> vector = Any[[1,2], [3,[4,5]]];

julia> typeof(vector[1])
Array{Int64,1}

I think this is the expected behavior. The manual says:

Arrays can also be directly constructed with square braces; the syntax [A, B, C, ...] creates a one dimensional array (i.e., a vector) containing the comma-separated arguments as its elements. The element type ( eltype ) of the resulting array is automatically determined by the types of the arguments inside the braces. If all the arguments are the same type, then that is its eltype . If they all have a common promotion type then they get converted to that type using convert and that type is the array’s eltype . Otherwise, a heterogeneous array that can hold anything — a Vector{Any} — is constructed; this includes the literal [] where no arguments are given.

4 Likes

Sudete already answered your question, but I still find it interesting to trace what happens here:
Incidentally:

julia> [[1,2],[3,[4,5]]]
2-element Array{Array{Any,1},1}:
 [1, 2]
 [3, [4, 5]]

but:

julia> [[1,2],[[3,],[4,5]]]
2-element Array{Array{T,1} where T,1}:
 [1, 2]
 [[3], [4, 5]]

It boils down to the following:
Julia uses promote_rule to find a common type for the array elements:

julia> typeof([1,2])
Array{Int64,1}

julia> typeof([3,[4,5]])
Array{Any,1}

julia> promote_rule(Array{Int64,1}, Array{Any,1})
Array{Any,1}

which ultimately calls this to decide which type to promote to:

julia> Base.el_same(Any, Array{Int64,1}, Array{Any,1})
Array{Any,1}

from here: https://github.com/JuliaLang/julia/blob/c31d126ac7ee425f3e121c16016844fb64a0c950/base/range.jl#L949

which basically says:
If one of the containers doesn’t need to change in type, take that one. If that doesn’t work, make a typejoin. (a typejoin would give you your expected result here, an Array{T,1} where T)

Ultimately, someone decided that it is better to have a concrete type if possible and only fallback to Union types if that doesn’t work.
That makes me think it’s intended behaviour, maybe necessary for good performance. I can’t give you more information on whether that is fully intended. Rules come from this commit:

https://github.com/JuliaLang/julia/pull/22757

So (and I hope thats not inappropriate) we might need to ask @jeff.bezanson about this.

5 Likes