Periscope Up!

Brujo Benavides
Erlang Battleground
4 min readJun 21, 2016

--

This Erlang battle-story owns its name (and its presence on the list) to Iñaki Garay. I struggled with it once, of course, and so did he. But he also noticed how this single battle-story acted as some sort of rite of passage for Erlangers. Once you’ve found this and struggled a while with it long enough, you can consider yourself a true erlanger.

Nullah (Brandon Walters) — Australia (2008)

The Task

There are many ways in which this story can unfold. Let me choose one that I remember: It all begins with a simple task…

Given a list of groups of people, count how many individuals from a particular country you have.

So, the initial version of a function that solves the problem may look like this:

The important function there is count/2. We added a test/0 function for good measure and if you try and run that function on an Erlang shell you’ll see something like this:

1> c(people).
{ok,people}
2> people:test().
ok

So far so good, but count/2 is deeply nested and some linting tools like Elvis will tell you that you should not write code like that. So, you go ahead and do some refactoring…

Function Heads

The first thing you might notice is that you have a case on a single variable inside an anonymous function that has itself a single argument: that same variable. Functions in Erlang (even anonymous ones) can have multiple clauses that work almost exactly as case clauses. It’s easy to see that you can remove a level of nesting by using that knowledge and rewrite count/2 as follows:

At first glance, that seems to be semantically the same as what you had before. But if you try to test it on a console…

2> people:test().
** exception error: no match of right hand side value 5
in function people:test/0 (people.erl, line 27)
3>

Suddenly, what used to be 0 (people:count(us, Groups)) is now 5.

Every Erlang newbie at this point in the story

List Comprehensions

Before explaining the issue, let me show an alternative way to implement the function, this time using one of my favourite Erlang constructs: List Comprehensions.

Look how nice, concise and clear it seems. You can almost read it: From the list of Groups pick the ones with country := Country, grab its Members, compute their lengths and sum all up. It’s almost exactly what the task required in the first place.

Well, as you might have guessed by now, if you try to test it you’ll see this:

9> people:test().
** exception error: no match of right hand side value 5
in function people:test/0 (people.erl, line 24)

What’s going on here?

Before giving up and reverting back to the original implementation, let me tell you that there is a way around the problem. Let me show you:

These 2 versions work as expected.

Now, walk away from Medium and try to figure out what happened on your own. When you’re done, come back and let me share with you the same lesson Iñaki Garay has shared multiple times at InakaESI with several Erlangers that walked this same path.

In Erlang, three things create a new scope for already bound variables: a function clause head, an anonymous function clause head and a list comprehension generator

In other words, since a case clause head is an expression that’s neither a function clause head, nor an anonymous function clause head nor a list comprehension generator, it does not shadow (i.e. re-bind) your variable. Whatever bound variable you use in a case clause head will remain bound. That’s not the case with function clause heads and neither it is with list comprehension generators. Nevertheless, you can totally use already bound variables in function clause guards and list comprehension filters and they’ll remain bound.

Caveat Emptor

For the sake of story-telling I omitted a crucial factor here: When you try to compile the code in the versions that do not work as expected, the compiler actually emit some pretty informative warnings like these ones:

1> c(people).
people.erl:13: Warning: variable ‘Country’ is unused
people.erl:16: Warning: variable ‘Country’ is unused
people.erl:16: Warning: variable ‘Country’ shadowed in ‘fun’
{ok,people}

The thing is more often than not the first 2 don’t appear because you actually use those variables somewhere else and the one about shadowing is ignored because…

  • you have removed the warnings using erlc -W0 or other similar configuration.
  • this function is part of a huge project and you have lots of output when you compile it (for instance, using rebar3 or erlang.mk) and you tend to ignore said output because it’s usually full of irrelevant warnings.
  • you just don’t understand what shadowing actually means.

--

--