[erlang-questions] comma vs andalso

Richard Carlsson richardc@REDACTED
Wed Jul 8 14:06:05 CEST 2009


Thomas Lindgren wrote:
> ----- Original Message ----
>> From: Richard O'Keefe <ok@REDACTED>
>> That is, in a guard, an expression using 'andalso' or 'orelse' is
>> still an expression, and if an exception occurs in the left-hand
>> operand, the whole expression is still skipped.
> 
> Guards are an awful mess in Erlang. I'd be quite surprised if the
> above difference in behaviour was intentional. If so, what possible
> purpose does it serve?

It is intentional in the respect that 'andalso'/'orelse' behave just
like any other operator in a guard test. As for purpose, all Erlang's
operators are allowed in guard expressions, so why make an exception?

> At one point, you could also use "and"/"or", the boolean operators
> manque, in guards. (I haven't checked recently whether this is still
> the case.) So we then have three very similar sets of guard
> operators.

'and'/'or' have always (as far back as I can remember, anyway) been
allowed in guards, again, probably simply by virtue of being general
operators in the language. And they don't behave like ','/';' either.
So to add to Richard O'Keefe's examples:

 h(X) when is_atom(element(3, X)) or true -> 42.

which for h(99) behaves just like the orelse version, i.e., throws
a function_clause error. Quite simply, and/or/andalso/orelse behave
just like +, ==, or any other operator; compare e.g.:

 i(X) when is_atom(element(3, X)) == true -> 42.

> Not to mention the twin set of type tests, introduced, as far as I
> know, to get rid of the lone double entendre of float/1 (as a test or
> conversion not the most frequent of operations).

That was one details, yes, but the main reason was to make it possible
to refactor a piece of code without being forced to change the names
of the function tests if you moved an expression from within a guard
to an ordinary expression and vice versa. Recall that before the is_...
versions, there was no way of saying e.g., "Bool = (is_integer(X) and
is_atom(Y))" without writing a case/if or a separate predicate function.

The old type tests didn't correspond to any built-in functions, so you
had to treat them as "primops" inside the compiler, you couldn't refer
to them and pass them around, etc. But the old type test names "atom(X)"
and so forth could not simply be made to work outside guards because
there would be name clashes (with the float(X) bif and with any existing
code that defined a function such as list(X) or integer(X)), hence the
is_... prefix for the generally usable versions that are proper built-in
functions (defined in the 'erlang' module along with all the others).

Now that we have the new is_-forms, the old forms are merely an obstacle
to refactoring. As a simple example, this macro nowadays works in both
guard expressions and in general expressions - it didn't back then:

  -define(is_a_foo(X), (is_integer(X) or is_binary(X))).

(we can even use orelse here, and it will still work in a guard).

> And now, for our convenience, the shorter form of these tests is being
> deprecated.

Hold your horses - nobody is deprecating the use of ',' and ';'. But
as Richard O'Keefe has rightly described, they *are* different from
the plain boolean operators.

A guard has the following general form:

  ... when  *, ..., *  ;  *, ..., *  ;  ...  ->

that is, one or more semicolon-separated alternatives, each consisting
of one or more comma-separated _tests_ (marked with a * here). Each test
is (conceptually) evaluated in a try/catch that simply transforms any
exception into 'false'. (Subexpressions of a test are evaluated as
normal, which is why 'orelse' does not continue executing if there
is an exception in the left-hand expression.) This fail-to-false
behaviour was in my opinion a mistake, because it occasionally hides
a bug in the guard and turns what should have been an observable runtime
crash into a silent "well, we take the next clause then". Some people
like to use this as a trick to write more compact guards, but that
makes it hard for someone reading the code to tell whether the
crash-jumps-to-the-next-case is intentional or not.

However, Erlang seems to be stuck with these semantics of guards. But
it means that if you do things inside your guard test like element(N,T),
length(Xs), or anything else that could crash if it gets bad arguments,
you need to think about the effect on what clause, if any, will be
selected. The thing to remember is that exceptions always stop at the
top of each separate test in the guard. Complicated expressions that
might throw an unwanted exception are generally better evaluated before
the switch, so that bugs are not hidden.

I'm not sure I have any real arguments against nesting of ','/';',
but I fear the grammar could get rather messy, and that such nested
guards could be quite difficult to read in practice.

    /Richard


More information about the erlang-questions mailing list