How would you explain Elixir immutability?

Hello guys,

I have a fairly simple question and cant seem to understand it.

for example we have the following code:

x = 1
x = 2
IO.puts x

since elixir is immutable, why does this print 2?
now i know that elixir doesnt make any change directly to x and in fact x = 2 is does on a copy of the variable x = 1. Then how can we get the original value for x, which is 1?

2 Likes

Immutability does not mean that you cannot rebind values. It means you cannot reassign as in other languages.

For example:

x = 1
if some_true_value do
  x = 2
end

IO.puts x # -> will output 1.

What you did in your example is just bound the value of 2 to the variable x, thus the original value of 1 is lost.

3 Likes

Immutability is independent from assignments – the act of creating the relationship between variable and the value it represents. Immutability only means your values (in memory) will never be modified. It doesn not mean that the variable x will always represent the same value. Once you changed what x points to there’s no way to get the value it pointed to before (even when it might still exist in memory).

7 Likes

the example you provided means that value of x is being changed scope wise. For example:

x = 1
if some_true_value do
  x = 2
  IO.puts x # -> will output 2.
end

IO.puts x # -> will output 1.

so like in Rust, it gives an error and also gives you the option to declare a variable mutable or immutable, should we be getting an error when we something like the code i posted?

1 Like

You’ll get an error if you bind a variable, but don’t use it within the scope it’s assigned in, but not if you use it as well.

1 Like

@jacknorman consider the response by @LostKobrakai, it is much more correct.

Another example which you might run in to when you are new to Elixir or immutabillity in general, is that you actually forget to reassign, e.g. when using lists and maps.

animals = [:bear, :elephant]
# no bears!
Enum.reject(animals, fn animal -> animal == :bear)
IO.inspect(animals) # -> [:bear, :elephant]

# so instead
animals = Enum.reject(animals, fn animal -> animal == :bear)
IO.inspect(animals) # -> [:elephant]
1 Like

alright, so that means that values can be overwritten in elixir for variables but memory wise the values stay the same? can you please explain with an example

1 Like

this I understand. The same concept is used when working with for loops in elixir, or when using sockets in live views and you have to do something like this for example

socket = socket |> assign()
1 Like

Indeed. As for the example, I am no expert in Elixir either… But what I understand is that values get assigned to variables and get scoped into the block they are defined in.

So

x = 1  # variable x is created and the value 1 is assigned
x = 2  # variable x is reassigned with value 2, same memory address
if :foo == :foo do
  x = 3 # variable x is created inside if block, think of the name as x' 
end
# here x is still 2, x' from inside the if block is gone.

@LostKobrakai does that make sense?

If you wanted the value from inside the if block, you’d need to assign is:

x = 
  if :foo == :foo do
     3
  else
     4
 end

x will be 3

1 Like

its a good answer but sadly still doesnt answer my question.

@LostKobrakai and @jeroenbourgois I think immutability means that whatever variable we are creating can be changed but when we look at it in the view of a process then their states cannot be changed once they are running. I saw this video and in this he explains that actually a copy is being made which is used but the original data stays the same.

1 Like

In short, both xes in your example are different variables.

It is more like:

x@1 = 1
x@2 = 2
IO.puts(x@2)

(It will look almost exactly like that in Erlang code)

The whole pattern is called rebinding and Rust offers something similar (if that make it clearer):

let x = 1;
let x = 2;
println!("{}", x);

Just in case of Elixir you do not need the let prefix.

To show that more explicitly in Elixir you can do:

x = 1
fun = fn -> x end
x = 2

IO.inspect(x, label: :x) # => 2
IO.inspect(fun.(), label: :fun) # => 1
14 Likes

Maybe something more along the lines of this will help:

a = [:bears, :elephants]
b = a
a = Enum.reject(a, fn animal -> animal == :bears end)
IO.inspect(a)
IO.inspect(b)

So we make a point to some value, and then make b point to the same thing as a. Then we call Enum.reject the result of which a gets rebound to. The reject call creates a new list based on what a was originally bound to: it cannot modify what a was originally bound to since that original list is immutable. b is still bound to the original list that a was originally bound to. Since that original list is immutable, we can make assumptions about b without concern with what happens to a later on. a itself may be rebound, but that original list of [:bears, :elephants] is not changed as a result.

9 Likes

Love these examples @hauleth and @sbuttgereit. I have a good grasp on this but never know how to explain it to people in a way they’ll accept. I think these will help—thanks!

4 Likes

Different languages have different meanings for things.

I’m most comfortable with julia’s definitions, where they make a distinction between variable and value. The variable is the lexical representation in code, and value is the value that the machine sees (the stuff in memory, if you will).

In this lexicon (which I think corresponds more to what we typically mean in math and CS), in Elixir, the variable is mutable but the value is immutable. You can ‘point’ your variable at a different value (“rebinding”), but this does not change the underlying values. If a different variable is holding onto the underlying value, then it is unaffected by the rebinding event.

Note that in the Elixir docs and the elixir community, we have a different meaning of variable, hopefully pointing this out will help you instead of confusing you.

9 Likes

In a nutshell: you can repoint x to a new value, but you can’t change the value x is currently pointing at.

Integers are not generally mutable in any language, so this is not a great example to demonstrate Elixir’s immutability with. Something that’s very mutable in other languages are collections, but in Elixir you cannot mutate a collection. Ie. in Elixir there’s no way to push a new value onto an existing list/tuple/map, instead you are always creating a new collection. Eg.

iex(22)> x = y = { 1, 2 }
{1, 2}
iex(23)> x = Tuple.insert_at(x, 0, :foo)
{:foo, 1, 2}
iex(24)> x
{:foo, 1, 2}
iex(25)> y
{1, 2}

Notice how we can reassign the new tuple we created with the new element inserted to x, but the tuple x had originally been pointing at (also assigned to y) remained unchanged.

4 Likes

Sometimes it’s easier to work it backwards.

So first accept that Elixir is immutable, which means variables cannot change, and values and data structures cannot change. Then work back from there.

So in your example, x = 1 and x = 2 seems contrary to immutability. But like others said, it is because these are actually two different variables.

The consequence is that you cannot do typical counting/summing using mutable methods, e.g.:

// This typical JavaScript way of summing would not work in Elixir due to immutability

function sumAll(list) {
  let sum = 0;
  for (let item of list) sum += item;
  return sum;
}
1 Like

Hi,
The concept of Immutability in Elixir is value of any variable(in memory) will never modified.
For example,
x = 1
x = 2
When we print x, it’ll print 2. It doesn’t means x modified its value but in memory new value(2) allocate to variable x.

Think of a variable as a label applied to a value. The label can be pulled off and placed on some other value; but the value itself cannot change. That is a fundamental property of the Erlang VM.

You can think of the = operator as the label operator. It’s not copying the value into a box for the variable name: the variable name is pointing to the actual value.

Check out this code

x = 1
y = 1

In that code x and y aren’t holding two copies of 1: they are each pointing to the same immutable value of 1. When you “relabel” x = 2 you are pulling the “x” label off of the value 1 and applying it to the value 2.

Maybe using strings will make it more clear.

> x = "abc"
"abc"

> x <> "def"
"abcdef"

> x
"abc"

When we concatenated “def” onto the variable “x” we did not change its value and we did not reapply the label “x” to a new value. Hence x still points to the same immutable value “abc”

If we reapply the label “x” to a new value

x = x <> "def"

Then the original value of “abc” is still unchanged and immutable. But we’ve pulled the label x off its original value and applied it to the new result.

1 Like

Understanding variable scope is also crucial.

This used to be part of the Elixir guides:
https://elixir-lang.readthedocs.io/en/latest/technical/scoping.html

I think what has changed is the scope in if/2, the rest stayed the same.