Trouble with `append!`

I often want to add an item to the end of a list (i.e., to a Vector). This works fine:

julia> x = [1,2,3,4]
4-element Vector{Int64}:
 1
 2
 3
 4

julia> append!(x,5)
5-element Vector{Int64}:
 1
 2
 3
 4
 5

But this does not:

julia> x = ["do", "re", "mi", "fa"]
4-element Vector{String}:
 "do"
 "re"
 "mi"
 "fa"

julia> append!(x,"sol")
ERROR: MethodError: Cannot `convert` an object of type Char to an object of type String

I realize I could do this instead:

julia> x = vcat(x, "sol")
5-element Vector{String}:
 "do"
 "re"
 "mi"
 "fa"
 "sol"

but the behavior of append! for lists of strings seems like a bug.

append! is for appending one collection to another, you should be using push! for both your use cases.

6 Likes

And the reason why append! works in the first case is that numbers are iterable. As it happens so are strings, but they iterate over their characters, which causes the type mismatch in the second case.

4 Likes

I think what’s particularly confusing here is that single numbers are “iterable”, yielding a single element.

For example

x = 42
for i in x
    println(i)
end

will print 42.

Looking at the method hit for append!(x, 5), we see

julia> methods(append!, Tuple{Vector{Int}, Int})
# 1 method for generic function "append!" from Base:
 [1] append!(a::AbstractVector, iter)
     @ array.jl:1187

Same if we checked for args of Tuple{Vector{String}, String}.

So when using append!, julia will try to iterate through the second argument, adding it’s elements to the first argument. The confusing part happens when iterating through a number yields a single number of the same type, while iterating though a string yields a sequence of Char, which is of a different type from the element type of the first argument.

Thanks. That makes sense. Or I can do this:

julia> x = ["do", "re", "mi", "fa"]
4-element Vector{String}:
 "do"
 "re"
 "mi"
 "fa"

julia> append!(x, ["sol"])
5-element Vector{String}:
 "do"
 "re"
 "mi"
 "fa"
 "sol"

Yes. But you should probably do the following if you know that you only have one element.

julia> x = ["do", "re", "mi", "fa"]
4-element Vector{String}:
 "do"
 "re"
 "mi"
 "fa"

julia> push!(x, "sol")
5-element Vector{String}:
 "do"
 "re"
 "mi"
 "fa"
 "sol"
1 Like

This works, but creates an unnecessary temporary array, which takes extra time and memory.

1 Like

If one insisted on using append! (for whatever reason), one could do

append!(x, ("sol", ))

to avoid the superfluous allocation.

2 Likes