pic
Personal
Website

5c. Assignments vs Mutations

PhD in Economics

Introduction

The upcoming sections will be devoted to the concept of vector mutation. To properly cover this subject, we first need to introduce some preliminary concepts, including:

  • the distinction between assignments and mutations

  • methods for initializing arrays to eventually mutate them

  • techniques for vector indexing to select elements

The current section in particular distinguishes between assignments and mutations of variables. The difference between them can easily go unnoticed by new users, as both operations use the operator = despite being fundamentally different. Clearly delineating these concepts is important not only in Julia, but also in other programming languages such as Python.

Some Background

Recall that a variable serves as a label for an object, which in turn holds a value. Additionally, objects can vary in the number of elements contained, ranging from single-element objects (e.g. integers and floating-point numbers) to collections (e.g. vectors).

NUMBER

VECTOR

The distinction between objects and their elements is crucial for the remainder of the section. This is because assignments are operations related to objects, whereas mutations are related to elements. More specifically, assignments rebind variables to new objects, while mutations simply modify existing elements.

Assignments

Assignments associate a variable with an object. Their implementation relies on the operator =, as in statements like x = 3 or x = [1,2,3]. In these examples, 3 and [1,2,3] represent the objects being assigned.

The concept of assignments becomes clearer when we consider an object as a memory address. From this perspective, a statement like x = [1,2,3] can be viewed as a two-step process: i) identifying a memory location to store the object with value [1,2,3], and ii) labelling that memory location x for convenient access.

Mutations

Assignments associate the name x to an object, regardless of whether the object is a number or a vector. However, vectors add the possibility of modifying an object's elements, without creating a new object.

The distinction leads us to two applications of =: as assignments (e.g., statements like x = [4,5,6]) and as mutations (e.g., statements like x[1] = 0). Despite sharing the same operator =, these operations are fundamentally different. Specifically, an assignment changes the object that x is bound to, so that it points to a new memory address. On the contrary, a mutation modifies the contents of the object, but without changing its memory address.

As an example, suppose we run the command x = [4,5]. This creates an object with value [4,5], which is then stored in some particular memory location. If we subsequently run x = [6,7], x will be associated with a new object holding [5,6], thus constituting an assignment. Conversely, if we execute x[1] = 0, the operation modifies the object [4,5] and transforms it into [0,5]. However, the object will still be pointing to the same memory address, thereby constituting a mutation.


MUTATION

ASSIGNMENT


Remark
You can mutate all the elements of x by referring to x[:]. Importantly, this doesn't entail a new assignment. In other words, modifying the values of x[:] does not change the object that x points to.

Code

x = [4,5]

x[:] =  [0,0]

Output in REPL
julia>
x
2-element Vector{Int64}: 0 0

This becomes important when we study approaches to speeding up operations, as mutations tend to be faster than creation of new objects.

Alias vs Copy

In Julia, the statement y = x means that y is an alias for the object referenced by x. This means both y and x serve as different labels for the same underlying object. Formally, it's said that y constitutes an alias of x. Importantly, y = x should not be interpreted as y being a label for x. This subtle distinction carries a significant practical implication: reassignments of x do not affect y.

To illustrate this, suppose we execute x = 2 and then y = x. This makes both x and y point to the same object holding 2. If we then execute x = 4, x would point to a new object that holds the value 4, but without changing what's stored in the original object of x. Consequently, y will still reference the object with value 2, as y is still pointing to the same object. This behavior is illustrated in the example below.


CORRECT

INCORRECT

CONSEQUENCE

NOT THE CONSEQUENCE

Code

x = 2   #'x' points to an object with value 2
y = x   #'y' points to the same object as 'x' (do not interpret it as 'y' pointing to 'x') 

x = 4   #'x' now points to another object (but 'y' still points to the object holding 2)

Output in REPL
julia>
x
4
julia>
y
2
Remark
Notice that two variables could comprise identical elements and yet refer to different objects.

This can be formally seen through the operators == and ===, which assess two types of equality. Specifically, x == y evaluates whether x and y have equal values, regardless of whether they refer to the same object. In contrast, x === y checks whether both x and y point to the same object, interpreted as a specific memory address. By applying these operators, the following example demonstrates that identical elements are not necessarily the same objects.

Code

x = [4,5]

y = [4,5]
z = x

Output in REPL
julia>
x == y
true #`x` and `y` have identical elements
julia>
x === y
false #`x` and `y` DO NOT point to the same object

julia>
x == z
true #`x` and `z` have identical elements
julia>
x === z
true #`x` and `z` DO point to the same object

Graphical Representation

We've indicated that the operation y = x creates an alias of x, making y and x two different labels for the same object. As a result, modifying the elements of either x or y will necessarily change the elements of both x and y simultaneously. This is illustrated in the following diagram and code snippet.

Graphical Representation

Variable y as an Alias

x = [4,5]
y = x

x[1] =  0

Output in REPL
julia>
x
2-element Vector{Int64}: 0 5
julia>
y
2-element Vector{Int64}: 0 5

If you'd like x and y to be treated as separate objects, you must use the function copy. This function generates a new object that contains the same elements as the original, ensuring that modifications to the new object don't affect the original.

Variable y as a Copy

x = [4,5]
y = copy(x)

x[1] =  0

Output in REPL
julia>
x
2-element Vector{Int64}: 0 5
julia>
y
2-element Vector{Int64}: 4 5