[erlang-questions] comma vs andalso

160 views
Skip to first unread message

Joel Reymont

unread,
Jul 6, 2009, 7:13:04 AM7/6/09
to Erlang Users' List
I noticed that inets code uses

foo(...) when Condition1 andalso Condition2 -> ...

Is the comma deprecated? For example, why not

foo(...) when Condition1, Condition2 -> ...

A related pet peeve of mine is that the following does not properly
indent in the latest Emacs mode

handle_info({tcp, Socket, Bin}, State)
when State#state.transport /= undefined,
State#state.socket == Socket ->
...

I have to manually insert spaces to align State#state... above and
using andalso above does not help either.

Thanks, Joel

---
Mac hacker with a performance bent
http://www.linkedin.com/in/joelreymont


________________________________________________________________
erlang-questions mailing list. See http://www.erlang.org/faq.html
erlang-questions (at) erlang.org

Magnus Henoch

unread,
Jul 6, 2009, 7:58:57 AM7/6/09
to Erlang Users' List
Joel Reymont <joe...@gmail.com> writes:

> A related pet peeve of mine is that the following does not properly
> indent in the latest Emacs mode
>
> handle_info({tcp, Socket, Bin}, State)
> when State#state.transport /= undefined,
> State#state.socket == Socket ->
> ...
>
> I have to manually insert spaces to align State#state... above and
> using andalso above does not help either.

I found that this happens because there is a "." in the guard. This
change to erlang-mode (actually erlware-mode, but the code should be
similar) seems to fix it:

diff --git a/erlang.el b/erlang.el
index 6ad03ea..916037e 100644
--- a/erlang.el
+++ b/erlang.el
@@ -2097,7 +2097,7 @@ Value is list (stack token-start token-type in-what)."
((looking-at "when[^->\.]*case[^->\.]*->"))
((looking-at "when[^->\.]*receive[^->\.]*->"))
;; Normal when case
- ((looking-at "when [^->\.]*->")
+ ((looking-at "when [^->]*->")
(erlang-push (list 'when token (current-column)) stack))
((looking-at "after[.]+->")
(erlang-push (list 'icr token (current-column)) stack))

Not sure what other cases that would mess up, though. The comment above
mentions one possible gotcha:

;; In test suites you may want to do something like
;; ?match(Mem when integer(Mem), mnesia:table_info(Tab,
;; memory)), and then the following if/case/receive
;; statement will mess up the indentation by fooling the
;; erlang mode to think the 'when' in the argument is a
;; "real" when. The following three clauses will avoid
;; this problem.

--
Magnus Henoch, mag...@erlang-consulting.com
Erlang Training and Consulting
http://www.erlang-consulting.com/

Roberto Ostinelli

unread,
Jul 6, 2009, 8:19:59 AM7/6/09
to Joel Reymont, Erlang Users' List

On 06/lug/09, at 13:13, Joel Reymont wrote:

> I noticed that inets code uses
>
> foo(...) when Condition1 andalso Condition2 -> ...
>
> Is the comma deprecated? For example, why not
>
> foo(...) when Condition1, Condition2 -> ...

the difference is very simple. comma indicates that both guard
operators need to be evaluated, while on the contrary 'andalso' means
that the second condition is evaluated ONLY if condition1 is true.
same goes for 'orselse'.

this is particularly relevant when condition2 would result for
instance in an evaluation error if condition1 is not met, as in:

foo(..) when (islist(X) == true ansalso (lists:nth(1,X) == 'test') -> ..

hope this clears.

r.

Roberto Ostinelli

unread,
Jul 6, 2009, 8:21:46 AM7/6/09
to Roberto Ostinelli, Joel Reymont, Erlang Users' List

On 06/lug/09, at 14:19, Roberto Ostinelli wrote:

> foo(..) when (islist(X) == true ansalso (lists:nth(1,X) == 'test') -
> > ..

that would be

foo(..) when (islist(X) =:= true) ansalso (lists:nth(1,X) =:= 'test') -
> ..

pardon me i was in python mode.

Martin Engström

unread,
Jul 6, 2009, 9:07:56 AM7/6/09
to Erlang Users' List
On Mon, Jul 6, 2009 at 2:21 PM, Roberto Ostinelli <rob...@widetag.com>wrote:

>
> On 06/lug/09, at 14:19, Roberto Ostinelli wrote:
>
> foo(..) when (islist(X) == true ansalso (lists:nth(1,X) == 'test') -> ..
>>
>
> that would be
>
> foo(..) when (islist(X) =:= true) ansalso (lists:nth(1,X) =:= 'test') -> ..
>

'==' and '=:=' are equivalent except when applied to numbers, are they not?
(1 == 1.0) is true, while (1 =:= 1.0) is false. When applied to atoms, as in
your example, there is no difference.
And the difference between comma and 'andalso' doesn't actually matter much
for the purpose of avoiding evaluation errors in guard context, since such
an error in a guard just means that the guard fails. For instance, try this:
1> length(a).
** exception error: bad argument
in function length/1
called as length(a)
2> Foo = fun(X) when length(X) > 3 -> "Long list"; (X) -> "Short or no list"
end.
3> Foo(a).
"Short or no list"
Also, lists:nth(X) is an illegal guard expression. ;)

Roberto Ostinelli

unread,
Jul 6, 2009, 9:09:52 AM7/6/09
to Hynek Vychodil, Joel Reymont, Erlang Users' List

On 06/lug/09, at 15:00, Hynek Vychodil wrote:

>
> It seems you are still in python mode. lists:nth/2 is not allowed in
> guard expression.

you are definitely right :)

let me point another (and correct) example then:

foo(..) when X =/= 0 andalso 1/X > 0.1 -> ..

the following expression would fail instead:

foo(..) when X =/= 0, 1/X > 0.1 -> ..

Richard Carlsson

unread,
Jul 6, 2009, 9:10:16 AM7/6/09
to Roberto Ostinelli, Joel Reymont, Erlang Users' List
Roberto Ostinelli wrote:
> the difference is very simple. comma indicates that both guard operators
> need to be evaluated, while on the contrary 'andalso' means that the
> second condition is evaluated ONLY if condition1 is true. same goes for
> 'orselse'.

No, the comma separator in guards also uses short-cut evaluation
and will not evaluate the subsequent tests if one test fails. If
there are alternatives expressed with semicolon, the next such
alternative will be tried instead; otherwise, the whole guard fails.

What you describe is true for the 'and' operator compared to 'andalso'.

/Richard

Roberto Ostinelli

unread,
Jul 6, 2009, 9:25:50 AM7/6/09
to Hynek Vychodil, Joel Reymont, Erlang Users' List

On 06/lug/09, at 15:21, Hynek Vychodil wrote:

> It seems no difference for me:
>
> 1> F = fun(X) when X=/=0, 1/X > 0.1 -> true; (_) -> false end.
> #Fun<erl_eval.6.13229925>
> 2> F2 = fun(X) when X=/=0 andalso 1/X > 0.1 -> true; (_) -> false end.
> #Fun<erl_eval.6.13229925>
> 3> [ F(X) || X <- [0, 0.0, 0.1, 10, 9]].
> [false,false,true,false,true]
> 4> [ F2(X) || X <- [0, 0.0, 0.1, 10, 9]].
> [false,false,true,false,true]
>
> --
> --Hynek (Pichi) Vychodil

yes, at it has been pointed out by richard already:

On 06/lug/09, at 15:10, Richard Carlsson wrote:

> and will not evaluate the subsequent tests if one test fails. If
> there are alternatives expressed with semicolon, the next such
> alternative will be tried instead; otherwise, the whole guard fails.
>
> What you describe is true for the 'and' operator compared to
> 'andalso'.

my fault.

as you can see:

1> F3 = fun(X) when X=/=0 and (1/X > 0.1) -> true; (_) -> false end.
#Fun<erl_eval.6.13229925>
2> [ F3(X) || X <- [0, 0.0, 0.1, 10, 9]].
[false,false,false,false,false]

Richard Carlsson

unread,
Jul 6, 2009, 9:41:15 AM7/6/09
to Roberto Ostinelli, Hynek Vychodil, Joel Reymont, Erlang Users' List
Roberto Ostinelli wrote:
> let me point another (and correct) example then:
>
> foo(..) when X =/= 0 andalso 1/X > 0.1 -> ..
>
> the following expression would fail instead:
>
> foo(..) when X =/= 0, 1/X > 0.1 -> ..

Aye, but an exception in a guard test is always caught and
treated as if the test evaluated to "false". In this example,
there is no observable difference between the two.

/Richard

who...@gmail.com

unread,
Jul 6, 2009, 9:52:14 AM7/6/09
to erlang-q...@erlang.org
On Jul 6, 3:25 pm, Roberto Ostinelli <robe...@widetag.com> wrote:

>
> 1> F3 = fun(X) when X=/=0 and (1/X > 0.1) -> true; (_) -> false end.
> #Fun<erl_eval.6.13229925>
> 2> [ F3(X) || X <- [0, 0.0, 0.1, 10, 9]].
> [false,false,false,false,false]

Ah, but that's because the expression (0 and (1/X > 0.1)) is bad.
(I've learned to be very afraid of how strongly 'and' binds. ;))

By contrast,

25> F4 = fun(X) when (X =/= 0) and (1/X > 0.1) -> true; (_) -> false
end.
#Fun<erl_eval.6.13229925>
26> [ F4(X) || X <- [0, 0.0, 0.1, 10, 9]].
[false,false,true,false,true]

And to iterate my previous point,

28> F5 = fun(X) when 1/X > 0.1 -> true; (_) -> false end.
#Fun<erl_eval.6.13229925>
29> [F5(X) || X <- [0, 0.0, 0.1, 10, 9]].
[false,false,true,false,true]


Roberto Ostinelli

unread,
Jul 6, 2009, 9:58:13 AM7/6/09
to who...@gmail.com, erlang-q...@erlang.org

On 06/lug/09, at 15:52, who...@gmail.com wrote:
>
> Ah, but that's because the expression (0 and (1/X > 0.1)) is bad.
> (I've learned to be very afraid of how strongly 'and' binds. ;))

definitely interesting. i guess you've learned it the hard way :)

r.

Richard O'Keefe

unread,
Jul 6, 2009, 8:47:23 PM7/6/09
to Erlang Users' List

On Jul 7, 2009, at 12:19 AM, Roberto Ostinelli wrote:
> the difference is very simple. comma indicates that both guard
> operators need to be evaluated, while on the contrary 'andalso'
> means that the second condition is evaluated ONLY if condition1 is
> true. same goes for 'orselse'.

He's wrong, but he's not _crazy_.
Section 6.24 of the reference manual _ought_ to explain this
clearly, but doesn't.

--------------------------
6.24 Guard Sequences
A guard sequence is a sequence of guards,
separated by semicolon (;).
The guard sequence is true if at least one of the guards is true.
(The remaining guards, if any, will not be evaluated.)
Guard1;...;GuardK

A guard is a sequence of guard expressions,
separated by comma (,).
The guard is true if all guard expressions evaluate to true.
GuardExpr1,...,GuardExprN

--------------------------
I propose rewriting these paragraphs something like this:

A guard sequence is a sequence (G1;...;Gk) of guards
separated by semicolons. Guards are tested from left
to right. If some guard succeeds, the guard sequence
as a whole succeeds, and the remaining guards will not
be checked. If all guards fail, the guard sequence as
a whole fails.

A guard is a sequence (GE1,...,GEn) of guard
expressions separated by commas. Guard expressions
are tested from left to right. If some guard
expression fails, the guard as a whole fails, and the
remaining guard expressions will not be checked. If
all guard expressions succeed, the guard as a whole
suceeds.

A guard expression is a very special restricted
expression. It is said to succeed it it evaluates to
true. It is said to fail if it evaluates to anything
else or raises an exceptions. Exceptions never
propagate out of guard expressions, they are just taken
as lack of success.

(then continue with the existing third paragraph)

Andrew Thompson

unread,
Jul 7, 2009, 12:16:23 AM7/7/09
to Erlang Users' List
On Tue, Jul 07, 2009 at 12:47:23PM +1200, Richard O'Keefe wrote:
>
> I propose rewriting these paragraphs something like this:

<snip>

I found your explanation a *lot* clearer and even learned something
reading it, so I'd definitely agree that it's an improvement.

I'm still unclear as to where 'andalso' differs from a comma though, how
is

true, false.

any different from

true andalso false.

Andrew

Roberto Ostinelli

unread,
Jul 7, 2009, 2:09:17 AM7/7/09
to Richard O'Keefe, Erlang Users' List
On 07/lug/2009, at 02.47, "Richard O'Keefe" <o...@cs.otago.ac.nz> wrote:

>
> On Jul 7, 2009, at 12:19 AM, Roberto Ostinelli wrote:
>> the difference is very simple. comma indicates that both guard
>> operators need to be evaluated, while on the contrary 'andalso'
>> means that the second condition is evaluated ONLY if condition1 is
>> true. same goes for 'orselse'.
>
> He's wrong, but he's not _crazy_.
> Section 6.24 of the reference manual _ought_ to explain this
> clearly, but doesn't.

that is the paragraph i'd been referring to and apparently i had been
interpreting it wrongly.

i thought this had already been cleared yesterday and yes, an editing
as proposed would be a good thing, i might not be the only one doing so.

r.

Richard O'Keefe

unread,
Jul 7, 2009, 9:59:35 PM7/7/09
to Andrew Thompson, Erlang Users' List

On Jul 7, 2009, at 4:16 PM, Andrew Thompson wrote:
> I found your explanation a *lot* clearer and even learned something
> reading it, so I'd definitely agree that it's an improvement.

Purr.


>
> I'm still unclear as to where 'andalso' differs from a comma though,
> how
> is
>
> true, false.
>
> any different from
>
> true andalso false.

First, some historical background.
Erlang was influenced by Prolog via Strand-88.
(By the way, does anyone have a Strand manual they don't want?)
In Prolog, there is one and only one way to get and-then (,)
and one and only one way to get or-else (;). To oversimplify,
<disjunction> ::= <conjunction> {';' <conjunction>}...
<conjunction> ::= <primary> {',' <primary>}...
<primary> ::= ['\+'] ( <call> | '(' <disjunction> ')' )
<call> ::= <atom>['(' <arguments> ')']
One of the things I've left out of that is infix, prefix, and
postfix operators. So something like

cousin_sibling_or_self(X, Y) :-
( father(X, P) ; mother(X, P) ),
( father(Y, Q) ; mother(Y, Q) ),
( father(P, G) ; mother(P, G) ),
( father(Q, G) ; mother(Q, G) ).

is possible, where "or" is nested inside "and".

Let's return to Erlang.

This syntax was adopted for Erlang guards, *ALMOST*.
(It was also adopted for Erlang function bodies, almost.
The C-like use of comma in Erlang expressions is rooted
in Prolog's comma, not C's.)
The *ALMOST* part is that Erlang does not allow nesting.
You cannot write

f(X, Y) when (X == 1 ; X == 2), (Y == 1 ; Y == 2) ->

You aren't even allowed to write (GE1,GE2),GE3 but *must*
write GE1,GE2,GE3. Nor may you write G1;(G2;G3). Weird.
This has never made sense to me. Such combinations are
perfectly meaningful, and allowing them would make the
definition of some guard macros considerably easier.

The obvious thing to do is to remove this arbitrary
restriction.

That's not the path Erlang took. (Honestly, you couldn't
make this stuff up.) Instead, *new* operators 'andalso'
and 'orelse' (copied from SML, which is a fine language
with some fine compilers) were added.

Most of the time, you can use 'andalso' and 'orelse' in
a guard, where you would have used ',' and ';'. "Most of
the time" means "when under the normal rules of evaluation
you would certainly not have had an exception."

Let's take two apparently similar functions:

f(X) when is_atom(element(3, X)) ; true -> 42.

g(X) when is_atom(element(3, X)) orelse true -> 42.

By actual test, f(99) -> 42.
By actual test, g(99) -> a function_clause exception.

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.

The thing is that 'andalso' and 'orelse' can be used in
any expression, not just guards. Erlang is treating
them in guard expressions exactly the same way that it
treats them in any other expressions. And that's
_different_ from the way ',' and ';' are treated when
there's an exception.

I conclude that
(1) you had best be EXTREMELY cautious about using 'andalso'
and 'orelse' in guards; they are NOT drop-in replacements
for ',' and ';', and
(2) it is long past time that Erlang allowed nested use of
',' and ';' in guards.

Alpar Juttner

unread,
Jul 8, 2009, 2:21:41 AM7/8/09
to Richard O'Keefe, Andrew Thompson, Erlang Users' List

> I conclude that
> (1) you had best be EXTREMELY cautious about using 'andalso'
> and 'orelse' in guards; they are NOT drop-in replacements
> for ',' and ';', and
> (2) it is long past time that Erlang allowed nested use of
> ',' and ';' in guards.

(3) this topic should be discussed in details (with examples) in the
doc.

Alpar

Thomas Lindgren

unread,
Jul 8, 2009, 4:38:30 AM7/8/09
to Erlang Users' List


----- Original Message ----
> From: Richard O'Keefe <o...@cs.otago.ac.nz>
>
> Let's take two apparently similar functions:
>
> f(X) when is_atom(element(3, X)) ; true -> 42.
>
> g(X) when is_atom(element(3, X)) orelse true -> 42.
>
> By actual test, f(99) -> 42.
> By actual test, g(99) -> a function_clause exception.
>
> 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.

....


> I conclude that
> (1) you had best be EXTREMELY cautious about using 'andalso'
> and 'orelse' in guards; they are NOT drop-in replacements
> for ',' and ';', and
> (2) it is long past time that Erlang allowed nested use of
> ',' and ';' in guards.


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?

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.

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). And now, for our convenience, the shorter form of these tests is being deprecated. Sigh.

Best,
Thomas

Richard Carlsson

unread,
Jul 8, 2009, 8:06:05 AM7/8/09
to Thomas Lindgren, Erlang Users' List
Thomas Lindgren wrote:
> ----- Original Message ----
>> From: Richard O'Keefe <o...@cs.otago.ac.nz>
>> 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

Richard Carlsson

unread,
Jul 8, 2009, 8:30:58 AM7/8/09
to Thomas Lindgren, Erlang Users' List
Thomas Lindgren wrote:
> 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). And now, for our
> convenience, the shorter form of these tests is being deprecated.
> Sigh.

Oh, and I forgot this beauty, which is a consequence of the
doubleplusweird scope rules for the old type tests in guards:

1> if integer(42) -> ok; true -> no end.
ok
2> if integer(42) == true -> ok; true -> no end.
* 1: illegal guard expression

because, you see, it is only at the absolute top of the guard test
that the name 'integer(X)' can be used to refer to the type test.
Shove it into one side of an '==', and it is no longer recognized.
The new forms work everywhere:

3> if is_integer(42) == true -> ok; true -> no end.
ok
4> if is_integer(42) == true -> ok; true -> no end.
ok

And for my final trick, here's the old double entendre you mentioned:

5> if float(3.14) -> ok; true -> no end.
ok
6> if float(3.14) == true -> ok; true -> no end.
no

why, isn't it obvious? The first clause uses the type test float(X),
while the second ensures that 3.14 is cast to a float and then
compares the number to 'true'.

Sticking to the modern is_-forms of the type tests lets you stay sane.

/Richard

Robert Virding

unread,
Jul 8, 2009, 7:10:59 PM7/8/09
to Richard Carlsson, Thomas Lindgren, Erlang Users' List
2009/7/8 Richard Carlsson <rich...@it.uu.se>

> Thomas Lindgren wrote:
> > 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). And now, for our
> > convenience, the shorter form of these tests is being deprecated.
> > Sigh.
>
> Oh, and I forgot this beauty, which is a consequence of the
> doubleplusweird scope rules for the old type tests in guards:
>
> 1> if integer(42) -> ok; true -> no end.
> ok
> 2> if integer(42) == true -> ok; true -> no end.
> * 1: illegal guard expression
>
> because, you see, it is only at the absolute top of the guard test
> that the name 'integer(X)' can be used to refer to the type test.
> Shove it into one side of an '==', and it is no longer recognized.


It has nothing to do with scope rules really. In the original guards only
simple tests were allowed so there was no problem. It only became weird when
expressions were allowed.

Life was simpler in the old days! :-)

Robert

Robert Virding

unread,
Jul 8, 2009, 7:28:23 PM7/8/09
to Thomas Lindgren, Erlang Users' List
2009/7/8 Thomas Lindgren <thomasl...@yahoo.com>

> ----- Original Message ----
> > From: Richard O'Keefe <o...@cs.otago.ac.nz>
> >
> > Let's take two apparently similar functions:
> >
> > f(X) when is_atom(element(3, X)) ; true -> 42.
> >
> > g(X) when is_atom(element(3, X)) orelse true -> 42.
> >
> > By actual test, f(99) -> 42.
> > By actual test, g(99) -> a function_clause exception.
> >
> > 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.
> ....
> > I conclude that
> > (1) you had best be EXTREMELY cautious about using 'andalso'
> > and 'orelse' in guards; they are NOT drop-in replacements
> > for ',' and ';', and
> > (2) it is long past time that Erlang allowed nested use of
> > ',' and ';' in guards.
>
> 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?
>

The above difference is intentional! There is one fundamental and important
difference between using orelse/or or ';' and that is in the case of
exceptions. If an exception occurs in the case:

G1 ; G2 ; G3 ; ...

say in G1 then G1 will fail and G2 will be attempted, as an exception is
equivalent to failure. This is logical as they are separate guards. However,
if an exception occurs in:

G1 orelse G2 orelse G3 ...

in G1 again then the WHOLE guard expression will fail, G2 will never be
tried as it is part of the same expression. This IS intentional as using ';'
is exactly equivalent to separate clauses with the same RHS. So

f(...) when G1 ; G2 ; G3 -> <RHS>.

is the same as:

f(...) when G1 -> <RHS>;
f(...) when G2 -> <RHS>;
f(...) when G3 -> <RHS>.

This functionality was added for just this case. There is logic in the
madness. It was simple before with only simple guard tests, it only became
confusing when logical expressions were allowed in guards. They are
practical but were only added later, THEY are the odd man out. This is also
why originally there were no problems with type tests without is_ in guards.

Guards were always meant to be part of pattern matching and no more.

Robert

Richard O'Keefe

unread,
Jul 9, 2009, 1:32:27 AM7/9/09
to Richard Carlsson, Thomas Lindgren, Erlang Users' List
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.


\

Richard Carlsson

unread,
Jul 9, 2009, 4:02:04 AM7/9/09
to Richard O'Keefe, Thomas Lindgren, Erlang Users' List
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

Michael McDaniel

unread,
Jul 9, 2009, 11:05:25 AM7/9/09
to erlang-q...@erlang.org
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Thank you for this history and explanation, Robert. It makes it
much easier for me to remember and understand how the ';' works.
(and, by extension, the other guard separators)


~Michael

Thomas Lindgren

unread,
Jul 20, 2009, 11:42:29 AM7/20/09
to Richard Carlsson, Erlang Users' List

Sorry for dropping my end of this; here we go.

----- Original Message ----
> From: Richard Carlsson <rich...@it.uu.se>
>
> Thomas Lindgren wrote:

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

First, note that guards as such already are a limited form of boolean formulae, where comma is interpreted as conjunction/and, and semicolon is interpreted as disjunction/"or". Evaluation of guards was, I would suppose, inspired by logic programming: if the formula could be satisfied, the guard succeeded; if not, it simply failed. Thus, a guard like 1/X > Z/Y would not throw an exception when X or Y was zero, but simply fail (because the test is not true).

For reasons unknown (except to some of the very senior members of this mailing list), the initial form of guards only permitted conjunctions of expressions G = E1,E2,E3 ... Some time later, a limited form of disjunction was added, permitting us to write (G1 ; G2 ; ... ; Gn) where the Gi were conjunctions, but still not permitting arbitrary boolean formulae as guards: no disjunctions inside conjunctions, in particular. As I recall, this was because of parser problems. When and/or was being added to guards, I do recall suggesting that general boolean guards should be permitted, to be written using nested and/or. (This was discussed sometime in the 1997-1999 timeframe, right?) But they weren't.

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

Well, to be precise they have only been allowed since some time after and/or were introduced into the language. (At the time when lots of stuff was added, such as funs, records and so on.) I mention this because I think the mess arises from stuff having been added incrementally with different intents by various people over the years.

Moving on, no, indeed they don't behave the same, and I do consider that a problem. As a consequence, we have three subtly different ways to write boolean formulae in guards. (Sorry about the jargon, dear readers, I'm trying to stay away from "boolean expression" here since we already are using "expression" in another sense) And unfortunately, at this point, _none_ of the three ways actually permit writing full boolean formulae. I consider this a failure in how guards are designed, which is why I'm always complaining about it.

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

I seem to recall boolean BIFs for old-style type tests (atom/1, integer/1, ...) in older Erlangs, which would permit one to write (atom(X) and atom(Y)) in clause bodies. Perhaps I misremember; still, there is no technical reason to avoid them in favour of the longer new names, _except_ possibly the clash with float/1. In that regard, I would argue that it would have been far easier to rename the lone float conversion operation rather than all type tests.

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

But this doesn't solve the problem -- it merely shifts name clashes to another part of the name space. Nor is there anything inherently impossible about defining and providing the BIF erlang:atom/1 instead of erlang:is_atom/1.

So, to conclude: it seems to me as if keeping the short names would have been just about the same as the current approach, except saving three characters per type test.

> > And now, for our convenience, the shorter form of these tests is being
> > deprecated.
>
> Hold your horses - nobody is deprecating the use of ',' and ';'.

(I was talking about the short type tests here.)

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

See above for what I would think is the reasoning behind the classic semantics. The main drawback of explicit crashes in guards is that as a guard-writer you don't have a lot of opportunities to catch them. To catch and hide explicit crashes, you may then have to turn clause guards into case-expressions. (In this context, I'm tempted to instead make an argument for fullblown expressions in guards, but let's leave that little hairball for another day.)

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

Well, let me then register my strong vote for actually, finally implementing full boolean formulae in guards. Rather than making the code harder to read, it will become easier: there is no need to code around the issue when you actually need to compose disjunctions, and the tests themselves will be hidden in well-formed macros. Macros which as a bonus can be composed fairly nicely without obscure parsing errors. And, in contrast with using and/or/andalso/orelse, the composed guards will still behave like classic guards.

Finally, a question regarding the grammar issue: this seems superficially like adding two more operators to the expression operator precedence grammar. Is there more and worse than that?

Best,
Thomas

Thomas Lindgren

unread,
Jul 20, 2009, 11:51:38 AM7/20/09
to Robert Virding, Erlang Users' List

(This fits my view of the history of guards, fwiw.) I've left a longer version of the argument in a reply to Richard, but basically, I think you would have saved some effort and gained some clarity and expressiveness by first of all implementing nested ","/";" as the connectives.

Thomas Lindgren

unread,
Jul 20, 2009, 12:15:43 PM7/20/09
to Richard Carlsson, Richard O'Keefe, Erlang Users' List


----- Original Message ----
> From: Richard Carlsson <rich...@it.uu.se>
>...
> > 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).

In my opinion a very dangerous attitude in a language like Erlang, where mistakes in design get set in stone. Off the top of my head:

1. records eternally forced to be tuples because of the chosen API;
2. and/or being strict, meaning we had to introduce andalso/orelse;
3. packages vs module names as atoms leading to unsoundness;
4. (the whole guard mess with all its needless duplication, as discussed elsewhere)

or the following:

....


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

I do think that rule has been criticized for being wrong basically since day one. (At least I have considered it an awful bug since I first encountered it; it makes a hollow mockery out of scoping) So it's good to hear it's getting fixed.

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

Um, you do realize that the current deprecation of short tests is breaking miles and miles of code? We're hardly taking the path of least resistance here.

Best,
Thomas

Reply all
Reply to author
Forward
0 new messages