The other day I received an email from a friend who is coming to Elixir from Ruby. The subject line was “elixir immutability”.
I’m working throughout the elixir-lang.org tutorial and I cannot understand why the language is immutable AND I can keep re-assigning the different values to the same variable
I thought the definition of immutability is that you can’t do that! What gives?
Variables in Elixir are immutable like they are in Erlang. The thing you are seeing that looks like mutation (and drives many Erlangers crazy) is called rebinding (more here). So in your shell example, you didn’t actually change the value that
x pointed to. Instead, you created a brand new identifier with the name
x that pointed at a different value (
2). The old identifier
x is no longer available, but its old value (
1) remains in memory untouched until it is garbage collected. The following sample uses an anonymous function to show what’s happening a bit more clearly:
I’m not a fan of rebinding, but under the hood, Elixir plays by the same rules as Erlang. It may put you at ease to know that the Elixir compiler is taking your
x = 1,
x = 2 and mapping them to identifiers that do not clash such as
x1 = 1 and
x2 = 2 before it all goes to byte code.
Elixir’s rebinding doesn’t create any of the shared-mutable-memory concurrency problems of non-functional languages. I suppose José Valim felt the syntactic sugar of rebinding would make people feel more at home if they were coming from OO languages.”
And that explanation explains it perfectly. If you blog elixir, consider posting that because I didn’t “get it” from googling for it.
Well not quite…
I feel I need to go into why rebinding drives many Erlangers crazy, but first let’s start with how Erlang drives everyone else crazy. Rebinding was introduced in Elixir to avoid an ugly thing that happens in Erlang code when you need to perform a series of operations on some data. For example a dictionary in Erlang…
Yes, the intermediate variables
X3 are pretty darn ugly. It’s not just ugly; it’s potentially dangerous. Several times in Erlang, I’ve accidentally passed the wrong intermediate variable to a
dict and as a result “lost a step” in the transformation. Unless you make an unfortunate combination of multiple goofs, you will get a compiler warning saying you have “unused variables”. That compiler warning often saves the day. At any rate, everyone agrees this code is ugly.
Now back to Elixir. Elixir’s rebinding allows something more “normal looking” (to a non-FP developer anyway).
There we only have to keep up with one variable
x. While not as ugly, this code is still pretty ugly, and there is a better way. Elixir’s lovely pipe-forward operator (
|>) produces a much more elegant solution:
|> says: “take the output of the expression to the left and push it in as the first argument to the expression on the right”. This feels functional; this makes me happy. With the pipe operator, I don’t need/want the ability to rebind
x to different values.
The harm for Erlangers
Ok, but where is the actual harm in rebinding? The thing that drives Erlangers crazy is not aesthetics (obviously)…(rimshot)…(cheapshot).
Say we are Harry from the Harry Potter series. Ron Weasley is our buddy, and Voldemort will kill us on sight. We decide to code up a safety charm for our front door. We select Elixir and the ErlangVM because it is functional, declarative, and reliable. Here we go…
Yes, Ron is our friend and Voldemort is our enemy, and you-know-who is about to knock at our door. Our case statement should welcome our
friend (i.e. “Ron Weasley”) and curse/disarm our
enemy (i.e. “Voldemort”).
Wait! What the hell just happened?! Voldemort knocked at our door, and we say, “Come on inside, Voldemort.” Let’s check our variables (and our underpants) to see what just happened…
This is crazy. And we are dead. If we had written the following code instead, Harry Potter would have lived.
See the difference? Notice the
enemy. The hat
^ says “use the last pinned value of this variable.” Without the
^ the variable
friend wasn’t used as a guarding, declarative pattern match; instead it was used as a short-lived re-bound variable that held whatever was passed in. That first clause would always match no matter what was passed in, and as soon as the case statement fell out of scope the only evidence that
friend was ever equal to
our_response. That is subtle; that is dark magic. It is easy (especially for Erlangers who expect a match) to miss it. This will cause problems, and the upside is hard to see.
Another question: if we write this as a module, will the compiler save us with a helpful warning? Answer: maybe, or maybe not.
…and we compile
Hmm… Mixed bag. There is no warning about
friend because we (pure-dumb-bad-luck) happened to use the value in our response. We do get a compiler warning on line:9 because
enemy is not used in that clause (pure-dumb-good-luck). That might have been enough to clue us in to the bug. If not, when we run…
Knock, knock. Who’s there? You know. You-know-who?
Identifier rebinding is not harmful in the same way that mutable variables are harmful. It’s not going to jack up your parallel work. Rebinding does create a potential pitfall though, and it adds a diligence requirement (always a bad thing) when using pattern matching.
This is an ugly wart on a beautiful language. Fortunately, there is a solution that is well proven, and it doesn’t require Elixir to break its v1.0 contracts. In Erlang, if you write a
case in which a pattern will never match you get a warning
If Elixir were to do the same everyone wins (except Voldemort).