[erlang-questions] comma vs andalso

Richard Carlsson <>
Thu Jul 9 10:02:04 CEST 2009


Richard O'Keefe wrote:
> Let's see if we can make this clear:
>   `and` `or` `andalso` and `orelse` are allowed ANYWHERE
>   in a guard expression are are treated IDENTICALLY to
>   the way they are treated elsewhere.

Quite.

> Of those things that are accepted at all in guards,
> ONLY ',' and ';' are treated differently.
> 
>   X , Y    acts like true=X, Y
>   X ; Y acts a bit like case (catch X) of true -> true
>                ; _ -> Y end.

A bit, yes. Just recall that even in the ',' case, no exception
will ever escape the guard as a whole (not sure if this was implied
above). A more exact equivalence could be written:

   -define(wrap(A), try (A)=:=true catch _:_ -> false end).
   -define(comma(A,B), (wrap(A) andalso wrap(B))).
   -define(semi(A,B), (wrap(A) orelse wrap(B))).

>> 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.
> 
> It has never been clear to me why this was considered
> important.  It's not something I often do in Haskell or
> Clean, where guards _are_ perfectly ordinary expressions,
> yet I refactor Haskell code all the time.

Important... maybe not, but definitely nicer when you do want to do it.
It makes it easier to teach the language (even though those who grew up
with Erlang may find the changes uncomfortable). And there were as I
said also considerations such as why you could talk about erlang:'+'/2
but not erlang:'list(X)'. In all, it was worth doing.

>> 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.
> 
> It's _still_ the case that almost no non-trivial expressions
> can be moved into a guard.

True. But every bit helps. Ive never found "this does not solve all
problems" to be an argument for not making a partial improvement
(as long as it does not create an obstacle for future development).

> And the new names created clashes with any previously existing
> code that defined a function such as is_list(X).  In fact,
> given the obvious rule
> 
>     if a module defines or explicitly imports a function
>     F/N that matches the name of a built-in function,
>     emit a warning, and for that module pretend that the
>     built-in function does not exist
> 
> no code would have been affected at all, other than to get
> new *warning* messages.  A language may need to add new
> built-ins at any time.  Erlang has often done so.  Such a
> rule is valuable.

Yes, however, there was already a similar rule in place since
ancient times, and it stated that in the case you describe, the
built-in function takes precedence. Bummer. (We are now slowly
trying to phase out this old rule, though, taking baby steps.)

> So the argument for the "is_" prefix is a non-sequitur:  it
> was neither necessary nor sufficient to avoid code breakage.

Neither necessary nor sufficient, but likely. It's a game of
probabilities. I _had_ seen several existing modules that used the
name list(Xs), and float(X) was already in use as a BIF for casting.
In comparison, the is_ convention was much less likely to cause
clashes (indeed, I recall no reports of any such when we introduced
the new names). And the convention has kept working for those type
tests that were added later, e.g., is_boolean(X), is_bitstring(X).

>>> And now, for our convenience, the shorter form of these tests is being
>>> deprecated.
>>
>> Hold your horses - nobody is deprecating the use of ',' and ';'.
> 
> He didn't say that.  He meant that the convenient short tests
> like atom(X) are being deprecated in favour of the long names.

I'm sorry, I misread. But the "long" names are only 3 more characters,
and at least in my opinion, they improve readability.

>>  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".
> 
> There are arguments on both sides here.

Absolutely. But I can tell you that it's one of the least fun kind of
facepalm-inducing bugs that can happen. I've seen programs that have
been running for years and years that now and then took the wrong case
but nobody detected it. Or worse, always took the wrong case.

  > I've been asked to help with a C compiler for a transport-
> triggered architecture, so my micro-Erlang compiler is on
> indefinite hold.  But I did actually tackle this problem
> in the grammar.  The grammar rules for guards need to be
> separate from the grammar rules for expressions, but neither
> gets particularly "messy".  If I recall correctly, it took
> me about half an hour to make the changes.

I'll take your word for that. Good to know.

> Such guards _could_ be hard to read in practice, but there's
> no reason they _have_ to be.  It is certain that they would
> make _some_ guards clearer.  Consider this example:
> 
> -define(is_vowel(X),
>     ( X == $a ; X == $e ; X == $i ; X == $o ; X == $u )).

Could be nice, but I'm not so much worried about what a distinguished
computer scientist will do with it, as what will happen in a large
body of code written by well-meaning but average programmers. I'm
open to persuasion, though.

> But what if you want to define guard tests that *can't*
> be (ab)used as expressions?

I don't quite see the point in that, though. Do you feel a similar
urge when programming Haskell, that you'd like to be able to write
things that have a meaning in guards only?

     /Richard



More information about the erlang-questions mailing list