Tricky Question

about variable binding

At my job, I was tasked with the creation of a multiple choice exam for Erlang candidates and I decided to include a tricky question about variable bindings. Before adding the question, I decided to test it myself (you know… just in case) and I found something that might surprise you on first sight, although it’s totally obvious after the fact.

Not sure why this popped up on google image search for tricky questions, but… NASA!

The Question

Needless to say this question was finally not included in the exam so don’t count on getting an easy answer from this post. In any case, the question was:

Assuming no variables are bound before, which variables are bound after evaluating the following expression?
A = 1,
B = case rand:uniform() of
C when C < 0.5 -> C;
D when D >= 0.5 -> 1 - D
end,
A + B.
Options:
1. None
2. A and B 
3. A , B , C and D 
4. It’s impossible to know for sure

STOP RIGHT HERE! Think for yourself without booting up a shell and testing it… What’s the answer? Don’t peek!


The Answer

Let’s discard the obvious ones first:

It can’t be #1, since we’re clearly binding variables there. The only way that #1 could be true would be for the expression to fail. No suspense here: it doesn’t.

It can’t be #3, since code paths go either through a branch that doesn’t bind C or through a path that doesn’t bind D.

In my mind that meant it had to be #2. It made sense, C and D where just temporary variables used only in the case expression.

But, of course, that’s not the case…

1> A = 1,
1> B = case rand:uniform() of
1> C when C < 0.5 -> C;
1> D when D >= 0.5 -> 1 - D
1> end,
1> A + B.
1.063499807059608
2> b().
A = 1
B = 0.063499807059608
C = 0.063499807059608
ok
3> f().
ok
4> e(1).
1.3765011035828076
5> b().
A = 1
B = 0.37650110358280764
C = 0.37650110358280764
ok
6> f().
ok
7> e(1).
1.0869922372383418
8> b().
A = 1
B = 0.08699223723834182
D = 0.9130077627616582
ok
9>

As you can see, sometimes C is bound and sometimes D is bound, depending on which clause of the case is evaluated and, of course that’s true even if the evaluation of the case clause bodies doesn’t require the variables themselves:

10> case rand:uniform() of
10> C when C < 0.5 -> low;
10> D when D >= 0.5 -> high
10> end.
low
11> b().
C = 0.3006547812129776
ok
12> f(), e(10), b().
C = 0.12584026085863464
ok
13> f(), e(10), b().
D = 0.9295007071083405
ok
14>

What’s going on here?

As I said before, this might be surprising, but it’s actually pretty obvious and well documented. Matching variables in case clause headers is perfectly valid and it can be used for stuff like this:

B = case rand:uniform() of
C when C < 0.5 -> low;
C when C >= 0.5 -> high
end,
{B, C}.

It’s a very convoluted way of doing things, I don’t recommend it at all, and you can even make it a bit worse…

case rand:uniform() of
C when C < 0.5 -> B = low;
C when C >= 0.5 -> B = high
end,
{B, C}.

…but, style and maintainability concerns aside, this code is totally valid and what that means is that variables that are bound in case clauses (both in the head and the body) remain bound after the evaluation of the case expression. I know there is a more academic way to express this situation using words like closure or scope but I’ll leave that explanation to folks like iraunkortasuna who are much more capable than me.

With that in mind, it’s reasonable that in our original example either C or D (but not both) are bound after the evaluation of the whole expression. But then, you wonder, that code is extremely unsafe… shouldn’t Erlang warn me against it? Shouldn’t Erlang prevent me from writing such a non-deterministic thing?

And of course, it does! But it’s the Erlang compiler, not the shell, the one that warns you about things like this. And it is really smart. Check the following module, for instance:

This is our original code and, in this case, even when either C or D are unbound after the case statement, since they’re not used, the compiler says nothing.

But now check this other module:

In this case, the compiler will not even compile the module. Instead, it will fail with the following error:

x.erl:8: variable 'C' unsafe in 'case' (line 6)

We can go off a tangent talking about how usable or readable that error message is, but the fact that the module can’t be compiled is undeniably useful.

PRO TIP: If you see errors like this when trying to compile an Erlang module, pay attention to the two line numbers you have in the warning: 8 (where the unsafe variable is used) and 6 (where it might be bound).


OffTopic Shameless Plug

You still have time to register your team for free to participate in SpawnFest and win some really nice prizes!

SpawnFest is a 48 hour free online development competition for Beamers around the world. Your team (1–4 members) will get exactly one weekend (December 9th 00:00 UTC to December 10th 23:59 UTC) to create the best applications you can. You will be judged by a carefully-chosen group of elite judges (including Fred Hebert, Christopher Meiklejohn and Joe Armstrong) and you can win great prizes provided by our sponsors.

Register your team for FREE and show the world what you can do!

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.