Difference in types being inferred in Julia

First note that you should use push! not append! to add one element at the end of the vector (append! appends elements of the collection to another collection). Now I will concentrate on the main issue assuming you would have used push! in your code.

All elements of code have the same type:

julia> typeof.(codes)
4-element Array{DataType,1}:
 Tuple{Int64,var"#4#6"{String}}
 Tuple{Int64,var"#4#6"{String}}
 Tuple{Int64,var"#4#6"{String}}
 Tuple{Int64,var"#4#6"{String}}

julia> unique(typeof.(codes))
1-element Array{DataType,1}:
 Tuple{Int64,var"#4#6"{String}}

Even more - this type is concrete:

julia> isconcretetype.(typeof.(codes))
4-element BitArray{1}:
 1
 1
 1
 1

(which means that things are going to be type stable and fast, which is good)

In such cases a comprehension sets this type as eltype of the resulting vector.

The problem is that the (4, reverse!) tuple has a different type:

julia> typeof((4, reverse!))
Tuple{Int64,typeof(reverse!)}

so you cannot add it to codes vector, i.e.:

julia> push!(codes, (4, reverse!))
ERROR: MethodError: Cannot `convert` an object of type typeof(reverse!) to an object of type var"#4#6"{String}

Now how to solve it? Set an appropriate eltype for codes vector when creating it like this:

julia> codes = Tuple{Int, Function}[(i, x -> push!(x,j)) for (i,j) in my_list]
4-element Array{Tuple{Int64,Function},1}:
 (0, var"#7#8"{String}("wink"))
 (1, var"#7#8"{String}("double blink"))
 (2, var"#7#8"{String}("close your eyes"))
 (3, var"#7#8"{String}("jump"))

julia> push!(codes, (4, reverse!))
5-element Array{Tuple{Int64,Function},1}:
 (0, var"#7#8"{String}("wink"))
 (1, var"#7#8"{String}("double blink"))
 (2, var"#7#8"{String}("close your eyes"))
 (3, var"#7#8"{String}("jump"))
 (4, reverse!)

and all will work as expected.

Let me give a simpler example of the same problem, so that the issue is more clearly visible:

julia> x = [i for i in 1:3]
3-element Array{Int64,1}:
 1
 2
 3

julia> eltype(x)
Int64

julia> push!(x, 1.5)
ERROR: InexactError: Int64(1.5)
Stacktrace:
 [1] Int64 at ./float.jl:710 [inlined]
 [2] convert at ./number.jl:7 [inlined]
 [3] push!(::Array{Int64,1}, ::Float64) at ./array.jl:913
 [4] top-level scope at REPL[55]:1

julia> x = Float64[i for i in 1:3]
3-element Array{Float64,1}:
 1.0
 2.0
 3.0

julia> push!(x, 1.5)
4-element Array{Float64,1}:
 1.0
 2.0
 3.0
 1.5

and append! would work like this (continuing the last example):

julia> append!(x, [2.5, 3.5])
6-element Array{Float64,1}:
 1.0
 2.0
 3.0
 1.5
 2.5
 3.5

What a fascinating question! First, let's look at the output of the array comprehension:

julia> codes = [(i, x -> push!(x,j)) for (i,j) in my_list]
4-element Array{Tuple{Int64,var"#50#52"{String}},1}:
 (0, var"#50#52"{String}("wink"))
 (1, var"#50#52"{String}("double blink"))
 (2, var"#50#52"{String}("close your eyes"))
 (3, var"#50#52"{String}("jump"))

Intestingly, you can see that all the functions in the vector are called var"#50#52"{String}(SOMETHING). We can get the type of one of these functions:

julia> typeof(codes[1][2])
var"#50#52"{String}

And see that is indeed a subtype of Function:

julia> typeof(codes[1][2]) <: Function
true

In fact, it appears that the four functions are the same type:

julia> all(typeof(f) === typeof(codes[1][2]) for (i, f) in codes)
true

It therefore appears that, for the sake of efficiency, Julia creates a single function type and 4 instances of the function each referring to a different string.

In Julia, every function has its own type. As shown above, this anonymous function has the type var"#50#52"{String}. The array specializes, setting its element type to the most specific type applicable. Therefore, the array's element type is Tuple{Int64,var"#50#52"{String}}, as can also be seen in the first snippet above. This means that the array only can contain that function in particular!

The same happens if you create an array with a normal function:

julia> array = [reverse!]
1-element Array{typeof(reverse!),1}:
 reverse!

julia> push!(array, isodd)
ERROR: MethodError: Cannot `convert` an object of type typeof(isodd) to an object of type typeof(reverse!)

To solve this, you need to instantiate the array so that it can contain any function:

Tuple{Int,Function}[(i, x -> push!(x,j)) for (i,j) in my_list]

And then it works :)

j

Tags:

Julia