Misunderstanding of the copy() function

Hello! I come from a Lisp background and mostly when I change a parameter inside a function I prefer to have it modified only in that function.
Now from what I’ve seen from the bellow code, lst actually gets modified too:

lst = [1 2 3 4 5]

function f(x)
    x[1] = 0
    return x
end

display(lst)
display(f(lst))
display(lst)

 1  2  3  4  5
 0  2  3  4  5
 0  2  3  4  5

The workaround I found is to always make a copy like this:

function g(x)
    clone = copy(x)
    clone[2] = 0
    return clone
end

And it works as I intended, however that’s not the issue I have right now.
When I try to work with expressions, although the code is quite similar, for some reason even though I use copy, it still modifies like there was no copy at all.

expr_lst = [:(a(1)) :(b(2)) :(c(3))]

function ef(x)
    x[1].args[1] = :(d)
    return x
end

function eg(x)
    clone = copy(x)
    clone[2].args[1] = :(e)
    return clone
end

display(expr_lst)
display(ef(expr_lst))
display(eg(expr_lst))
display(expr_lst)

The output being:


 :(a(1))  :(b(2))  :(c(3))
 :(d(1))  :(b(2))  :(c(3))
 :(d(1))  :(e(2))  :(c(3))
 :(d(1))  :(e(2))  :(c(3))

What am I missing? Also how can I solve this issue (perhaps in a more elegant way without using copy every time)? I would imagine there can be a keyword that automatically makes a copy for each parameter inside the function.

copy only copies the outer collection. If the elements are mutable, then effectively what you get is a new collection with fresh pointers to the same mutable objects. Here’s an example of that phenomenon:

julia> x = [[1], [2]];

julia> y = copy(x);

julia> x === y
false

julia> y[1][1] = 100;

julia> x
2-element Vector{Vector{Int64}}:
 [100]
 [2]

There is deepcopy, but it’s not recommended. It’s better to explicitly construct a new object than to use deepcopy.

Maybe a more functional approach would help, like this:

julia> function change_f(ex)
           if ex.args[1] == :a
               :(d($(ex.args[2])))
           elseif ex.args[1] == :b
               :(e($(ex.args[2])))
           else
               ex
           end
       end
change_f (generic function with 1 method)

julia> exs = [:(a(1)) :(b(2)) :(c(3))]
1×3 Matrix{Expr}:
 :(a(1))  :(b(2))  :(c(3))

julia> map(change_f, exs)
1×3 Matrix{Expr}:
 :(d(1))  :(e(2))  :(c(3))

julia> exs
1×3 Matrix{Expr}:
 :(a(1))  :(b(2))  :(c(3))
2 Likes