Why tuples are immutable?

What is the fundamental reason behind? Is it because the elements can be of any datatype, so modification incurs a lot of memory relocation?

We need to understand what a Tuple is first.

help?> Tuple
search: Tuple tuple NTuple ntuple NamedTuple @NamedTuple

  Tuple{Types...}

  Tuples are an abstraction of the arguments of a function – without
  the function itself. The salient aspects of a function's arguments
  are their order and their types. Therefore a tuple type is similar
  to a parameterized immutable type where each parameter is the type
  of one field. Tuple types may have any number of parameters.

Since they represent the arguments of a function, we might think they are about to enter the function’s stack. The stack is an abstract model for a processor’s register memory. Basically, the compiler needs to know a fixed size for all the arguments to create this stack of memory.

Moreover, immutability has a number of beneficial consequences. Immutable objects do not have to be copied when passed around. Also their known size and memory layout means they packed together efficiently or inlined into a larger structure.

Because of those benefits, immutable structs are the default in Julia. You can kne of the justifications why in the pull request below.

9 Likes

Stack and (im)mutability are orthogonal concepts with no connection.

EDIT:
But I would add that true immutability is great for performance.
True immutability requires some heavy handedness. Rust does this well:
you can have either any number of immutable references to an object, or only a single mutable reference without any immutable references.
Because of this, in Rust, if you have an immutable reference, you know for a fact that no mutable references exist.
Without any mutable references existing, that thing really isn’t changing.

Julia takes a clunkier approach:
This object type only allows immutable references, or this object type only allows mutable objects. So you need two mirrored object types if you want both, and then to convert between them, which unfortunately isn’t free even when in theory the compiler should be able to optimize the conversion away (but that can be fixed in the future!).

This is in contrast to C++ for example, which is like Rust but without the borrow checker that makes sure no mutable references exist when you have an immutable reference. Thus, in C++, an immutable object can change because you’re modifying a mutable view of that object.
Rust disallows that because you can’t have both at the same time. Julia disallows that because for a particular object type, it is just either always immutable, or always mutable.

This lets the compiler optimize memory reads/writes.
As a simple example, in Julia and Rust, the following code only needs 1 load and store, but in C++ you might need 2 loads:

x = immutable_object[i]
mutable_object[j] = x
foo(immutable_object[i])

because modifying mutable_object could be modifying immutable_object in C++, forcing the reload. Obviously, you should write

x = immutable_object[i]
mutable_object[j] = x
foo(x)

instead to avoid that problem, but that could be clunkier in more complicated examples (imagine: tons of abstractions/function calls that get inlined and compile down to something like the above).

4 Likes

Sure, but you did not provide a better answer to the question: Why are tuples immutable?