Ah yeah this tripped me up at first as well. If you look at the help page for fill:
help?> fill
If x is an object reference, all elements will refer to the same object:
julia> A = fill(zeros(2), 2);
julia> A[1][1] = 42; # modifies both A[1][1] and A[2][1]
julia> A
2-element Vector{Vector{Float64}}:
[42.0, 0.0]
[42.0, 0.0]
All of the objects in A are the same (like pointing exactly to the same spot in memory). So when you update one, they all get updated.
I usually avoid this using a list comprehension:
julia> B = [zeros(2) for _ in 1:3]
3-element Vector{Vector{Float64}}:
[0.0, 0.0]
[0.0, 0.0]
[0.0, 0.0]
julia> B[1][2] = 5
5
julia> B
3-element Vector{Vector{Float64}}:
[0.0, 5.0]
[0.0, 0.0]
[0.0, 0.0]
I guess I was confused because it didn’t occur to me that the internal fill(0,2) would be creating a new object that persists. I would have realized what’s happening if I had written this instead:
I think this is a really important thing for people to come to Julia (especially from R) to realize. In Julia, whenever you call f(g(x))f only sees the result of g(x), not how it got there. This is why macros have special syntax. In R, all functions are the equivalent of macros in Julia, which adds a ton of magic that makes code less predictable. In Julia, there is a special syntax that warns the reader that something special is happening when a macro is used.
@Oscar_Smith, could you please explain a bit further what this fill()'s behavior has to do with f(g(x)), and why this is the most logical choice?
It looks like another trap that is very easy to fall into. Thanks.
A = fill([0,0], 3)
A[1][2] = 5
julia> A
3-element Vector{Vector{Int64}}:
[0, 5]
[0, 5]
[0, 5]
First, if all you want is a matrix, then fill(0, (2, 3)) suits your need.
If you really want an array of arrays, then you have to create independent arrays for each entry in the larger array (usually with list comprehension).
For your question, fill([1], 3) could be interpreted as such:
create an array that repeats a single object
the object is [1] in this case
this object will be repeated for 3 times
fill does not attempt to make copies of this object
But array comprehension [[1] for i in 1:3] is not taking [1] as an argument, instead, it takes a function i->[1] that produces a distinct array that only contains 1 each time it is called.
I agree that the behavior of fill([0, 0], 2) is counterintuitive and kind of pointless. This question comes up on a regular basis here on Discourse. I would favor changing this behavior for Julia 2.0.
this creates a 2-element vector, where both elements refer to the same object, namely the newly created zeros(2).
i.e. a[1] refers to the same object as a[2]. This is the behaviour of fill.
If you modify (mutate) that object, the change will be reflected when referencing that object via either a[1] or a[2] (since it is the same object).
a = fill(zeros(2), 2)
a[1][1] = 42.0 # note that a[2][1] = 42.0 will do the same thing
a
However, when you write a[1] = [1.0,1.0], you are “reassigning” a[1] to refer to the newly created object [1.0,1.0]. Hence, a[1] and a[2] now refer to different objects.
I also stumbled in the same way. This can cause someone to waste many hours looking for errors in their code, while the mistake was to rely too much on their own intuition.