Julia: Question about variable binding, mutating, and mutable functions

217 Views Asked by At

I am writing code in Julia which collects some output from a function foo (which mutates its input argument), and I'm trying to append the recursive evaluations from this function in an array A.

For instance, foo!(x) changes the value of x by adding 1 to each of its elements.

function foo!(x)
    x .= x .+ 1
    return(x)
end


julia> x = [1, 1];

julia> foo!(x);

julia> x
2-element Array{Int64,1}:
 2
 2

I want to create an array A which stores the value of x = f(x) over a fixed range. However, A just ends up containing multiple copies of the final value of f(x), e.g.,

julia> x = [1, 1];

julia> A = [x];

julia> for i=1:3
           push!(A, foo!(x))
       end

julia> A
4-element Array{Array{Int64,1},1}:
 [4, 4]
 [4, 4]
 [4, 4]
 [4, 4]

I'm trying to get it to get it to efficiently output something similar to

julia> B
4-element Array{Array{Int64,1},1}:
 [1, 1]
 [2, 2]
 [3, 3]
 [4, 4]

I haven't been able to find a helpful resources for a developing a solid understanding of mutations, or the order in which mutations are executed in Julia. Any help in this regard would be greatly appreciated!

2

There are 2 best solutions below

1
Nils Gudat On BEST ANSWER

The way you've written it, you repeatedly push! the same object into A, which your foo! function mutates:

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

julia> A = [x]
1-element Vector{Vector{Int64}}:
 [1, 1]

julia> foo!(x)
2-element Vector{Int64}:
 2
 2

julia> A
1-element Vector{Vector{Int64}}:
 [2, 2]

One way of fixing this is to copy the elements in A before x gets mutated:

julia> for i ∈ 1:3
           A[i] = copy(x)
           push!(A, foo!(x))
       end

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

A classic read on values vs. bindings can be found here.

1
David Sainez On

By using only push! you are just creating an array of references to a single array (ie x). This is why you see the same value repeated many times.

If you want to keep copies of the value of x across invocations of foo! you can use copy:

julia> foo!(x) = x .+= 1
foo! (generic function with 1 method)

julia> x = [0,0];

julia> A = [copy(foo!(x)) for i in 1:4]
4-element Vector{Vector{Int64}}:
 [1, 1]
 [2, 2]
 [3, 3]
 [4, 4]