What's the use of immutability upon rebind?

What’s the use of data immutability upon rebind when you can not access the unbinded data in Elixir?

Immutability has a lot of benefits that are orthogonal to variable bindings. Consider this example from Ruby:

irb(main):001:0> a = {hello: "world"}
=> {:hello=>"world"}
irb(main):002:0> b = {greeting: a}
=> {:greeting=>{:hello=>"world"}}
irb(main):003:0> a[:foo] = 'bar' 
=> "bar"
irb(main):004:0> b
=> {:greeting=>{:hello=>"world", :foo=>"bar"}}
irb(main):005:0> a = 1
=> 1
irb(main):006:0> b
=> {:greeting=>{:hello=>"world", :foo=>"bar"}}

Notice how by changing a we also essentially made an implicit and hidden change to b. Later we rebind a but b remains changed. In Elixir this doesn’t happen:

iex(1)> a = %{hello: "world"}
%{hello: "world"}
iex(2)> b = %{greeting: a}
%{greeting: %{hello: "world"}}
iex(3)> Map.put(a, :foo, "bar")
%{foo: "bar", hello: "world"}
iex(4)> b
%{greeting: %{hello: "world"}}

Notice that this holds true even if we rebind a:

iex(5)> a = Map.put(a, :foo, "bar")
%{foo: "bar", hello: "world"}
iex(6)> b
%{greeting: %{hello: "world"}}

Importantly, immutability means that b does NOT have to copy the original a value in order to make this work. Rather, maps are persistent data structures, and so b can continue pointing to a's original place in memory. When we “update” a that’s when a spot in memory is allocated for the new key value pair. If values could be updated in place, we’d either get ruby like behavior, or every data structure would always have to deep copy every other datastructure it pointed to.

10 Likes

In short variables can only update itself?

Not quite, you’re confusing the name of a variable with the value of the variable. No values in immutable data structures are updated ever, it doesn’t matter whether you use the same name or not.

When you rebind, you’re simply saying “this code identifier no longer points to the value it used to, now it points to this other value”. The actual value that it used to point to however doesn’t change.

You asked the question “what’s the point”. I wasn’t really sure what this meant, so I tried to highlight some characteristics of immutable data structures that have nothing to do with questions of binding.

4 Likes

Even for a variable of the same name it’s just a rebinding:

iex(1)> a = 1
1
iex(2)> x = fn -> a end
#Function<20.127694169/0 in :erl_eval.expr/5>
iex(3)> a = 2
2
iex(4)> x.()
1
4 Likes

Unbound data is garbage-collected. You usually get an updated version of the now-unbound data.

1 Like

Well, as long as it isn’t referenced. In a list of length n for example, there are n - 1 [h | tail] cells that are unbound, and none of those are garbage collected, since they’re pointed to by something that is bound.

2 Likes

In asynchronous or parallel programs (race conditions) how do one guarantee it gets the first binding of a variable if it wants to?

The unit of computation in erlang and elixir is a process. Processes receive messages and act on those messages. Only the process has access to the data inside of it. Messages are processed sequentially. All of these things taken together help to eliminate a large number of data races. The data in the process isn’t shared. It can only be copied to a different process.

2 Likes

You’re very hung up on bindings, but they’re entirely irrelevant here. The compiler ultimately turns everything into single assignment form anyway. IE:

a = %{foo: 1}
a = 1

compiles to

a0 = %{foo: 1}
a1 = 1

But again, this isn’t important. What’s important is that a data structure can reliably hold a pointer to another data structure and be guaranteed that the value at the other end of that pointer will never change. Bindings have nothing to do with that property.

3 Likes

Generally, there are no race conditions in most Erlang/Elixir programs because no data is shared (unless you go out of your way to share them). Data is copied when it’s needed.

1 Like

At the risk of quibbling, what Erlang/Elixir avoids are “Data races” wherein shared memory is put or read in an invalid state through simultaneous access by different threads. This is impossible since, as you note, there’s no shared memory that can be accessed in that way. Anyone can write a program that contains race conditions though, that’s a wildly broad category.

3 Likes

You are not quibbling, I am just not that well versed in the exact terminology. :slight_smile:

The OP seems worried that certain FP concepts make software less useful. By answering like we all do here we are hopefully clearing the confusion.

1 Like