Allow Pattern = Guard_Expression as a simple guard test. Make obviously silly guards syntax errors.
Replace the opening text of section 6.24 “Guard Sequences” as follows.
<guard> ::= <OR guard>
<OR guard> ::= <AND guard> {';' <AND guard>}*
An <OR guard>
is a sequence of <AND guards>
separated by
semicolons. Here, as elsewhere in Erlang, semicolon means
sequential OR: an <OR guard>
evaluates its <AND guards>
one at a time from left to right, until one succeeds or
until all have failed.
<AND guard> ::= <guard test> {',' <guard test>}*
An <AND guard>
is a sequence of <guard tests>
separated by
semicolons. Here, as is often the case in Erlang, comma
means sequential AND: an <AND guard>
evaluates its
<guard tests>
one at a time from left to right, until all
have succeeded or one has failed.
<guard test> ::= <guard match>
| <Boolean expression>
<guard match> ::= <pattern> '=' <guard expr>
| <pattern> '=' <guard match>
A <guard test>
is either a match or a Boolean expression.
In a guard, a match succeeds if and only if the <guard expr>
can be evaluated without exception, and the result can be
matched with the <pattern>
, possibly binding some variables.
If a variable is bound in one <guard test>
, it may be used in
later <guard test>
s of the same <AND guard>
. If a variable
is bound in all of the <AND guard>
s of an <OR guard>
it may
be used in the guarded code, so
if X = 1, is_atom(element(X, Tup))
; X = 2, is_atom(element(X, Tup))
-> ... uses X ...
is OK. If a variable is bound in one of the <AND guard>
s of
an <OR guard>
but not all of them it may not be used in the
guarded code, so
if X = a
; Y = b
-> ... uses X ...
is not allowed.
A <Boolean expression>
in a guard consists of a number
of subexpressions
constant 'false'
constant 'true'
variable (must be bound to 'false' or 'true')
term comparison with `<guard expr>` operands
calls to type test BIFs with `<guard expr>` operands
`<Boolean expression>`s in parentheses
combined using the operators ‘not’, ‘and’, ‘or’, ‘andalso’, and ‘orelse’. Thus
X+1 == Y
is a <Boolean expression>
that can be used as a <guard test>
but
X+1
is not. You are advised never to use the ‘and’ and ‘or’ operators and to avoid ‘andalso’ and ‘orelse’ whenever ‘,’ and ‘;’ will do what you need.
The set of <guard expr>
s is a subset of the set of valid Erlang
expressions. The reason for restricting the set of valid
expressions is that evaluation of a guard expression must be
guaranteed to be free of side effects and to terminate.
A <guard expr>
consists of a number of subexpressions
<guard expr>
arguments<guard expr>
s in parenthesescombined using the built in arithmetic and bitwise operators.
There are two parts to this EEP. It was originally going to be just about allowing matches in guards. Then it was going to be two, because the current situation is just too messy, but then it became one again for brevity.
Consider this case. A function is given a tuple and an index. If the element at that index is in the range 0..127, it should be returned. Otherwise some other clause should apply. Currently, we have to write
f(Tuple, Index)
when is_integer(element(Tuple, Index)),
0 =< element(Tuple, Index),
element(Tuple, Index) =< 127
-> element(Tuple, Index);
...
or something else which is even clumsier. Why can’t we write
f(Tuple, Index)
when X = element(Tuple, Index),
is_integer(X), 0 =< X, X =< 127
-> X;
...
In trying to explain how to add this to the language, I found that the current description of guards in the Erlang reference manual is remarkably fuzzy. Dismayingly, this is matched by an equally fuzzy implementation. The description mixes up things that can be used as arguments of guard BIFs (guard expressions) with simple guards.
Consider the example
X = 1,
if X+1 -> true
; X-1 -> false
end.
This clearly makes no sense at all, and should be rejected as bad syntax. According to the current reference manual, it is legal; X+1 and X-1 are legal “guard expressions”.
In the shell, this example crashes, which indeed makes a lot of sense. But ‘erlc’ says:
{X+1} Warning: the guard for this clause evaluates to 'false'
{X-1} Warning: the guard for this clause evaluates to 'false'
It is good that there is a warning, but bad that the text of the warning is wrong. These things DON’T evaluate to ‘false’, they evaluate to numbers. Then, despite having given a warning, you get a run-time error.
exited: {if_clause,[{a,f,0},{shell,exprs,6},{shell,eval_loop,3}]}
What happened in this example, of course, was that all of the clauses of the ‘if’ were eliminated because all of them were malformed. More realistic examples would simply quietly do the wrong thing at run time.
The syntax for allowing matches in guards is obvious; no other syntax would be tolerable. The only real question is whether they can be embedded inside ‘andalso’ and ‘orelse’ or not, and in order to avoid questions of backtracking, I have said “no”. This is really the simplest extension of guards to allow matches that I can think of.
The rest of the EEP is concerned with trying to rule out obviously silly guard tests at compile time. Precisely how this is done is debatable. That it should be done surely isn’t. What benefit do we currently obtain (other than unwarranted simplicity in the compiler) from allowing “27” and “X+5” as guards?
Matches are currently not allowed in guards, so no existing application code can be broken by adding them. Obviously, anything that works with Erlang parse trees will need to be extended.
Cleaning up what’s allowed in guards may affect existing code. However, in most cases the compiler would already have warned about this, and the compatibility issue amounts to turning a warning message into an error message.
None.
This document has been placed in the public domain.