Understanding immutability, bindings and evaluation

I am trying to understand bindings and the immutable nature of functional programming with this example.

iex(20)> a = 1
1
iex(21)> b = a + 1
2
iex(22)> a = a * 5
5
iex(23)> b
2
iex(24)> b + a
7
iex(25)> b
2

When I rebind a, b doesn’t change. From what I understand about functional languages, b gets bound to the expression a + 1. I would expect setting a = a * 5 to result in the value of b to be 7 as the value of a has changed.

Is this result of Elixir’s greedy evaluation, in that because b was assigned the evaluation of a + 1 at the time, rebinding a wouldn’t change it? Does that also imply if a + 1 was not greedily evaluated, the value of b would have changed after a was reassigned?

Assuming that iex did not evaluate a + 1 as soon as I pressed the Enter key, and I did something to evaluate b after I set a to a * 5, would a subsequent attempt to evaluate b produce the result 7?

Does that also mean that another stricter functional language would not allow a to be rebound, unless the language had an explicit unbind command which Elixir handles implicitly/automatically?

I suppose there is conceptual different between a value and a binding or that in some purer languages there is no distinction.

1 Like

Is there some command in iex that I can use to inspect b to see how it was intially bound or evaluated?

Not in Erlang/Elixir. In this case, the expression gest evaluated, and value is assigned to “b”. The values in these cases are evaluated greedy, yes. Functional programming comes in many flavors, and not all languages employ lazy evaluation like that :slight_smile:

3 Likes

This is not correct. Elixir is not lazy. b = a + 1 immediately evaluates the expression a + 1, results in 2, and then binds the result to b.

I’m not sure that this is an answerable question. The entire language would be re-thought if it targeted lazy evaluation. The closest existing example we have of this is:

a = 1
b = fn -> a + 1 end
a = 2
b.() #=> this returns 2 not 3.

Don’t think of it as changing a. Everything you wrote originally is exactly equal to (and essentially becomes when fully compiled):

iex(20)> a0 = 1
1
iex(21)> b0 = a0 + 1
2
iex(22)> a1 = a0 * 5
5
iex(23)> b0
2
iex(24)> b0 + a1
7
iex(25)> b0
2

Rebinding is just a matter of shadowing the previous variable name, it does not alter its contents.

4 Likes

Are there some functional languages whose syntax allow you write b = a + 1 without a being bound so that so that when your evaluate b it would output a + 1 and only output the number 7 after you set a = 5, and only do so if you give it a special command like eval b?

I’m not sure. There are definitely lazy functional programming languages, but combining laziness WITH what would effectively be mutating the value of a seems like a strange (and in my view undesirable) pairing.

My last response is concerned with the lazy/greedy evaluation aspect, not the immutability part. I am just trying to see what immutability really means in terms of Elixir/Erlang evaluation. So I have to assume that Elixir’s rebinding of variables has some kind of syntactic dissonance for programmers from a more traditional functional programming background.

There is a comment by José Valim on a blog post by Joe Armstrong - Joe Armstrong - A week with Elixir - and not being familiar with functional programming paradigms I am trying to work out what it really means.

Something probably got you confused. Elixir does not have mutable state as you usually find in imperative programming. Lists, tuples and all other data types are immutable. The mutable state you get in Elixir is exactly the same you get in Erlang, nothing less, nothing more, which is usually via processes and you need to send messages to change or read from the process state (the actor model).

Some people think that rebinding variables makes Elixir unsafe, in the sense you could have race conditions, but that’s not true. Elixir assignments are compiled down to Static Single Assignment. More importantly, you also can’t change a function’s binding after the function is created.

In fact, any language that runs in the Erlang VM will have those semantics. You need to play along with the VM immutability semantics, it can’t be done otherwise.

That said, I don’t know from where you got the impression Elixir is mutable. Or got the impression we are trying to make “a better functional language”. Yes, it is a functional language, but we never claim it to be a better one (as it is a shallow goal).

In pure functional yes, it gets bound to the a that existed at the time that b was bound, once bound it will always be the same, if it could change it would not be very immutable would it. ^.^

No, in pure functional languages they would make a closure of the environment at that point that exists with that expression, so you get the same result whether greedy or lazy, just different performance.

You are not rebinding a, you are binding a ‘new’ a that is shadowing (hiding from this point forward) the old a. Think of it more like when you do a=0 then a=1 the compiler is actually doing a_0 = 0 and a_1 = 1, they are different, the physical name that you see in text does not exist, only its binding structure.

Yeah in purer languages there is no real distinction (though there is for performance characteristics).

Not that I’ve seen, I do not even know how that would work. You can do it with a ‘macro’ sure, but not a binding itself. A binding binds a name to an expression, and an expression hold the environment of the point it was defined.

Immutable means that once a binding is set, it is not changeable, ever, anything it refers to is unchangable, etc… etc… Once you bind, match, define, whatever anything it will never ever change. State is held via the stack.

1 Like

I personally think the easiest way to understand it is that variables just reference values. When you give a variable a value you bind it to the value. So with your original example:

iex(20)> a = 1
1
iex(21)> b = a + 1
2
iex(22)> a = a * 5
5
iex(23)> b
2
iex(24)> b + a
7
iex(25)> b
2

After 20 a is bound to (references) the value 1, after 21 b is bound to the value of a+1 which is 2, after 22 a will now be bound to the value of (old)a * 5 which is 5, in 23 and 24 b has not changed as is it still referencing the value 2. In this example the values are just integers but of course they can be any data structures.

Erlang, and hence Elixir, is strict and not lazy so all expressions are evaluated immediately which is why a variable cannot be bound to an expression. Not in the lazy functional sense anyway.

Another thing to remember with = is that it is not an assignment but a pattern match. On the RHS you have an expression while on the LHS you have a pattern. It works by first evaluating the expression then matching the value against the pattern. If the match succeeds then the variables in the pattern will be bound to values. Elixir allows you to rebind variables.

Hope this doesn’t confuse the issue even more.

7 Likes