There are Guards and Guards

The next one in my list of Erlang battle-stories was actually published in the erlang-questions mailing list and explained by Kostis Sagonas and Doug Rohrer, but it was so WAT-provoking that I couldn’t leave it out of my list.

It’s all about guards and precedence…


Diego Torres — La Furia (1997)

Boolean Operators in Guards

Not to use the same example that Roberto Ostinelli used in the mailing list, let’s do something that no-one else did before us. Let’s define factorial in Erlang:

1> Fact = fun F(0) -> 1
1> ; F(X) -> X * F(X-1)
1> end.
#Fun<erl_eval.30.52032458>
2> Fact(1).
1
3> Fact(10).
3628800

So far, so good. But you know the next step, right? Some wise dev comes in and tells us that our function hangs for negative numbers. So, we improve it…

4> f().
ok
5> Fact = fun F(0) -> 1
5> ; F(X) when X > 0 -> X * F(X-1)
5> end.
#Fun<erl_eval.30.52032458>
6> Fact(10).
3628800
7> Fact(-1).
** exception error: no function clause matching erl_eval:’-inside-an-interpreted-fun-’(-1)

That’s awesome, but to be fair, factorial only makes sense for integers. Now that we are at it, we should add that to our guard, right?

8> f().
ok
9> Fact = fun F(0) -> 1
9> ; F(X) when is_integer(X) and X > 0 -> X * F(X-1)
9> end.
#Fun<erl_eval.30.52032458>
10> Fact(2.0).
** exception error: no function clause matching erl_eval:'-inside-an-interpreted-fun-'(2.0)

All looks good and shinny, except that now…

11> Fact(10).
** exception error: no function clause matching erl_eval:’-inside-an-interpreted-fun-’(10)
I googled “guard wat” — Was not disappointed

What’s going on here?

You can, as usual, try to figure this one out for yourselves, although I must confess I haven’t done any research myself this time.

I’ll just state that I know it’s a matter of operator precedence, even when I’m not exactly sure what are the rules that apply here. What I do know are two examples that actually work as expected. This is one:

11> f().
ok
12> Fact = fun F(0) -> 1
12> ; F(X) when is_integer(X) and (X > 0) -> X * F(X-1)
12> end.
#Fun<erl_eval.30.52032458>
13> Fact(10).
3628800
14> Fact(10.0).
** exception error: no function clause matching erl_eval:’-inside-an-interpreted-fun-’(10.0)
15> Fact(-1).
** exception error: no function clause matching erl_eval:’-inside-an-interpreted-fun-’(-1)a

This is the other one:

21> f().
ok
22> Fact = fun F(0) -> 1
22> ; F(X) when is_integer(X) andalso X > 0 -> X * F(X-1)
22> end.
#Fun<erl_eval.30.52032458>
23> Fact(10).
3628800
24> Fact(10.0).
** exception error: no function clause matching erl_eval:’-inside-an-interpreted-fun-’(10.0)
25> Fact(-1).
** exception error: no function clause matching erl_eval:’-inside-an-interpreted-fun-’(-1)

I would like to wrap this up by quoting Kostis:

It's a long story. Its short version is that you will have a much better state of mind if you forget the presence of 'or' and 'and' in guards and use ';' and ',' when you can, and 'orelse' and 'andalso' when you cannot (as in your F fun).