Strange outcome for the Julia list with different type of elements

Here is the minimum working example.

julia> f= rand(Float32, 8)
8-element Vector{Float32}:
 0.31442094
 0.31442094
 0.2593087
 0.2593087
 0.11091578
 0.11091578
 0.14340413
 0.14340413

julia> i = rand(Int32, 8)
8-element Vector{Int32}:
 -1010112671
   346170778
   668418952
   -50293529
  1151894370
   208560393
 -1186865689
 -1973369622

julia> v_fi = [f, i]
2-element Vector{Vector{Float32}}:
 [0.31442094, 0.31442094, 0.2593087, 0.2593087, 0.11091578, 0.11091578, 0.14340413, 0.14340413]
 [-1.01011264f9, 3.4617078f8, 6.6841894f8, -5.029353f7, 1.1518944f9, 2.085604f8, -1.1868657f9, -1.9733696f9]

julia> any_v_fi = Any[f, i]
2-element Vector{Any}:
 Float32[0.31442094, 0.31442094, 0.2593087, 0.2593087, 0.11091578, 0.11091578, 0.14340413, 0.14340413]
 Int32[-1010112671, 346170778, 668418952, -50293529, 1151894370, 208560393, -1186865689, -1973369622]

julia> scalar_v_fi = [20, f, i]
3-element Vector{Any}:
 20
   Float32[0.31442094, 0.31442094, 0.2593087, 0.2593087, 0.11091578, 0.11091578, 0.14340413, 0.14340413]
   Int32[-1010112671, 346170778, 668418952, -50293529, 1151894370, 208560393, -1186865689, -1973369622]

In the above example, I am expecting Julia to preserve the data type of f and i in the v_fi same like any_v_fi. It should have used “Any” by default to the list for v_fi. Surprisingly, it applies “Any” to scalar_v_fi when there is one scalar element in the list. This behavior is not consistent.

1 Like

The element type of an array [a, b, c] is computed by the function promote_type(typeof(a), typeof(b), typeof(c)). What you’re observing is the following:

julia> promote_type(Vector{Int32}, Vector{Float32})
Vector{Float32} (alias for Array{Float32, 1})

julia> promote_type(Int, Vector{Int32}, Vector{Float32})
Any

That is, in v_fi, when you mix a Vector{Int32} and a Vector{Float32}, the promoted type is Vector{Float32}, and instances of Vector{Int32} are converted accordingly. In scalar_v_fi you’re also adding an Int into the mix, and now promote_type falls back to Any, hence no element is converted.

People may argue whether this behavior is desirable, but it is deliberate.

7 Likes

Basically, homogeneous containers (analogous to numpy arrays) are much faster than heterogeneous containers, so Julia usually prefers to promote to a common type if possible (it’s not possible if one element is a scalar and another is a vector).

But of course you can opt out if you specifically want Vector{Any} (the analogue of a Python list).

6 Likes

In many cases where you need to lump together things of different data type, like in a python list, a tuple is usually better in julia. It’s immutable, so you can’t change it, though you can change mutable elements inside it.

julia> f = rand(Float32, 10);
julia> i = rand(Int32, 10);
julia> fi = (f, i)
(Float32[0.3967855, 0.74455744, 0.14703006, 0.44867855, 0.48266786, 0.91201013, 0.28800505, 0.5439985, 0.06666976, 0.710635], Int32[-15829316, 604588000, 810921946, 929331054, 1339835954, -911415111, 142170320, 350952455, 327657902, -1265631069])

or a named tuple:

julia> nfi = (f=f, i=i);
julia> nfi.f
10-element Vector{Float32}:
...
julia> nfi.i
10-element Vector{Int32}:
...
julia> nfi.i[4] = 42
42
5 Likes

Thank you all for the replies and discussion. I agree that there are workarounds and alternatives to lump together different data types of elements either using tuple or Any. However, it will be simple if we preserve the type of elements by default if anyone uses Julia list even without Any. It will make it easy with simplified understanding of using Julia list. If we need the functionality of promote_type to all elements of list, a new function or keyword can be provided on list like promote.

Respectfully, I disagree. Automatically attempted promotion in the absence of an explicit element type in array literals has been the standard for a long time, especially used on subtypes of Number for improved performance:

julia> [1 2//3 4.5 6im]
1Ă—4 Matrix{ComplexF64}:
 1.0+0.0im  0.666667+0.0im  4.5+0.0im  0.0+6.0im

Removing automatic promotion would not only be breaking, it would require more typing in more common situations than typing a short Any in much rarer situations. Manually calling a promotion function would not be a good solution because you need to instantiate an unwanted Any array as an input. A new keyword would not be a good solution because it complicates array literal syntax; you can’t just put it in front of the brackets because that already designates the element type.

Your misuse of the word “list” for Arrays suggests your preference for no automatic promotion comes from a different programming language (we can’t assume it’s the one people have already mentioned). Of course it’d be easier for you to understand if Julia syntax behaved more like a language you’re used to, but it’s impossible for any one language to be similar to every language any user might come from. We have to adapt to different syntax and styles when going between languages, and Julia is no exception.

2 Likes