Notice how earlier, I define a variable a with value 1. I push this into a list. Change the value of the list variable but that doesn’t change ‘a’.
However, if I define a struct. And NOW push it into a list, and alter the list element. It changes the struct!
What’s happening? Is it normal and preferred behaviour or an artifact of the mutable type or @kwdef argument?
In the case of the mutable struct and a = Hello(5). You are assigning the first index of the list to be bound to the variable a. At which point you are mutating the variable bound to a, and not assigning a new value to the first index of the list.
The proper comparison of what you do with the mutable struct is what happens to your list in the first case. Note that the list element is modified, as the element of the struct, without changing the value bound to the label a:
julia> a = 1
1
julia> list = []
Any[]
julia> push!(list, 1)
1-element Vector{Any}:
1
julia> list[1] = 5
5
julia> list
1-element Vector{Any}:
5
julia> a
1
The above is equivalent, in this sense, to:
julia> mutable struct A
x
end
julia> s = A(a)
A(1)
julia> s.x = 5
5
julia> s
A(5)
julia> a
1
where the struct is playing the same role as the list, as being a mutable container of a data value.
There aren’t “normal” or “not normal” variables. There are mutable values and immutable values.
The mutable struct is mutable. Thus you can mutate it, for example by changing list[1].val = 5 as in your example. There, list[1] is a mutable struct of type Hello, and you are mutating the value of val inside it. The list itself is also a mutable type, thus why you can replace the values of its elements.
In your original example, you are mutating the value val of the mutable struct that is stored in the first element of list.
And, indeed, if you want to mutate the instance that is stored in list[1] without affecting it outside, you need to copy it. That is the same as:
julia> x = [1]
1-element Vector{Int64}:
1
julia> list = [ x ]
1-element Vector{Vector{Int64}}:
[1]
julia> list[1][1] = 0
0
julia> x
1-element Vector{Int64}:
0
julia> list = [copy(x)]
1-element Vector{Vector{Int64}}:
[0]
julia> list[1][1] = 2
2
julia> x
1-element Vector{Int64}:
0
Where x is taking the role of your mutable struct.
Yeah, I understand mutable structs are mutable. So is the variable ‘a’ though. My question was, when pushed into a list, why is a new element indexed in a list that would now be unrelated to the root variable ‘a’. Whereas in case of a mutable struct, the pushed value is bound to the struct, not assigning a new value.
@Daniel_Berge did point out this is how struct behaviour is defined
No, no, the a is just a label to a value. The value when a = 1 is 1 an that is an immutable value. If you push 1 to a list, the label a is still bound to the value 1. If you change the value of the list to something else (like 5), that does not affect the value to which the label a is bound.
This is the same with a mutable value:
julia> x = [1];
julia> list = [x]
1-element Vector{Vector{Int64}}:
[1]
julia> list[1] = [2] # bind list[1] to *another* value
1-element Vector{Int64}:
2
So, in this sense, they are exactly the same. The difference is that if you do, above:
julia> list[1][1] = 5
5
julia> list
1-element Vector{Vector{Int64}}:
[5]
you are mutatinglist[1], not assigning it to another value (mutable or not), as in the first case.
Ahhhhh now I get your point. Woah! I didn’t know that.
So any structure that inherently has its own elements… like a list, or a struct would be mutable? When pushed into a list, all variables are bound in their original form. When you redefine an element of the list, you just disconnect the variable. But if you mutate an element of the list’s element, does that mutate the actual struct that was bound in the first place?
No, not really. A struct can be immutable as well, then you simply cannot do that:
julia> struct A
x
end
julia> a = A(1)
A(1)
julia> list = [a]
1-element Vector{A}:
A(1)
julia> list[1].x = 1
ERROR: setfield!: immutable struct of type A cannot be changed
To change list[1] you need to replace the structure completely (identically to if it was a number):
julia> list[1] = A(2)
A(2)
julia> list
1-element Vector{A}:
A(2)
All right, I have to admit… I did not know that! Woah! a = 2 followed by a = 3 does not change the value of a. But rather makes a point to a different element!
Gotta admit, never saw it this way in my years of programming. Thank you! (love the Julia documentation lol)
Note that this is the behavior in Python and most (all?) other interactive languages. The language where that can be really thought differently (as a being the container of the value, rather than simply a label given to a value, is Fortran - on the surface everything in Fortran looks like mutable objects).