[erlang-questions] comma vs andalso

Richard O'Keefe <>
Thu Jul 9 07:32:27 CEST 2009


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.
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.

> 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.

> 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.

 > and with any existing
> code that defined a function such as list(X) or integer(X)),

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.

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

>  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).

But we _can't_ write

    -define(is_a_bar(X), (Y = X*X, (Y < -2 or Y > 2)).

and expect _that_ to work in both guards and expressions.
[Abstract patterns would solve this problem another way.]
>
>
>> 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.

>  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.
>

> 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.

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.

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 )).

count_adjacent_vowels(L) ->
     count_adjacent_vowels(L, 0).

count_adjacent_vowels([V|S=[W|_]], N)
   when ?is_vowel(V), ?is_vowel(W) ->
     count_adjacent_vowels(S, N+1);
count_adjacent_vowels([_|S], N) ->
     count_adjacent_vowels(S, N);
count_adjacent_vowels([], N) ->
     N.

This could also be done using `orelse`, of course.
But what if you want to define guard tests that *can't*
be (ab)used as expressions?

Right now, we can't even do

-define(is_probability(X),
   ( is_float(X), X >= 0.0, 1.0 >= X )).

f(X, Y)
   when ?is_probability(X), ?is_probability(Y) ->
     X*Y.


\



More information about the erlang-questions mailing list