Elixir Immutability

The other day I received an email from a friend who is coming to Elixir from Ruby. The subject line was “elixir immutability”.

###The question >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 >

 iex(21)> x = 1
 1
 iex(22)> x = 2
 2
 iex(23)> x
 2

I thought the definition of immutability is that you can’t do that! What gives?

###My response 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:

iex(1)> x = fn () -> "First one" end
#Function<2.90072148/0 in :erl_eval.expr/5>
iex(2)> z = x
#Function<2.90072148/0 in :erl_eval.expr/5>
iex(3)> x = fn () -> "Second one" end
#Function<2.90072148/0 in :erl_eval.expr/5>
iex(4)> x.()
"Second one"
iex(5)> z.()
"First one"
iex(6)>

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.”

###The reply > And that explanation explains it perfectly. If you blog elixir, consider posting that because I didn’t “get it” from googling for it.

Done!

###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…

Eshell V6.3  (abort with ^G)
1> X = dict:new().
{dict, ...
2> X1 = dict:append("A",1,X).
{dict, ...
3> X2 = dict:append("B",2,X1).
{dict, ...
4> X3 = dict:append("C",3,X2).
{dict, ...
5>

Yes, the intermediate variables X,X1,X2,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).

iex(1)> x = HashDict.new
#HashDict<[]>
iex(2)> x = Dict.put(x, "A", 1)
#HashDict<[{"A", 1}]>
iex(3)> x = Dict.put(x, "B", 2)
#HashDict<[{"A", 1}, {"B", 2}]>
iex(4)> x = Dict.put(x, "C", 3)
#HashDict<[{"A", 1}, {"B", 2}, {"C", 3}]>
iex(5)>

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:

iex(1)> x = HashDict.new |>
...(1)> Dict.put("A", 1) |>
...(1)> Dict.put("B", 2) |>
...(1)> Dict.put("C", 3)
#HashDict<[{"A", 1}, {"B", 2}, {"C", 3}]>
iex(2)>

The |> 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…

iex(1)> friend = "Ron Weasley"
"Ron Weasley"
iex(2)> enemy = "Voldemort"
"Voldemort"
iex(3)> knocking_at_our_door = "Voldemort"
"Voldemort"

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”).

iex(4)> our_response = case knocking_at_our_door do
...(4)>   friend -> "Come on inside, #{friend}."
...(4)>   enemy -> "Expelliarmus!"
...(4)> end
"Come on inside, 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…

iex(5)> friend
"Ron Weasley"
iex(6)> enemy
"Voldemort"
iex(7)> knocking_at_our_door
"Voldemort"
iex(8)> our_response
"Come on inside, Voldemort."

This is crazy. And we are dead. If we had written the following code instead, Harry Potter would have lived.

iex(4)> our_response = case knocking_at_our_door do
...(4)>   ^friend -> "Come on inside, #{friend}."
...(4)>   ^enemy -> "Expelliarmus!"
...(4)> end
"Explelliarmus!"

See the difference? Notice the ^friend versus friend and ^enemy versus 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 Voldemort is 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.

defmodule KnockKnock do 
  def who_is_there do
    friend = "Ron Weasley"
    enemy = "Voldemort"
    knocking_at_our_door = "Voldemort"

    our_response = case knocking_at_our_door do
      friend -> "Come on inside, #{friend}."
      enemy -> "Expelliarmus!"
    end

    {friend,enemy,knocking_at_our_door,our_response}
  end
end

…and we compile

iex(1)> c "knock_knock.ex"
knock_knock.ex:9: warning: variable enemy is unused
[KnockKnock]

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…

iex(2)> KnockKnock.who_is_there
{"Ron Weasley", "Voldemort", "Voldemort", "Come on inside, Voldemort."}
iex(3)>

Knock, knock. Who’s there? You know. You-know-who?

###Conclusion 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 beak its v1.0 contracts. In Erlang, if you write a case in which a pattern will never match you get a warning

Warning: this clause cannot match because a previous clause at line 11
always matches 

If Elixir were to do the same everyone wins (except Voldemort).

Phoenix Channels via F#

My 2016 F# Advent calendar post Continue reading

NDC Oslo 2015 - The FP Track Cheatsheet

Published on June 16, 2015

NDC London 2014 - The FP Track Cheatsheet

Published on December 02, 2014