Kijewski
Kijewski

Reputation: 26043

When to prefer `and` over `andalso` in guard tests

I am curious why the comma ‹,› is a shortcut for and and not andalso in guard tests.

Since I'd call myself a “C native” I fail to see any shortcomings of short-circuit boolean evaluation.

I compiled some test code using the to_core flag to see what code is actually generated. Using the comma, I see the left hand value and right and value get evaluated and both and'ed. With andalso you have a case block within the case block and no call to erlang:and/2.

I did no benchmark tests but I daresay the andalso variant is the faster one.

Upvotes: 35

Views: 11622

Answers (5)

Arvind Kumar Avinash
Arvind Kumar Avinash

Reputation: 79540

When to prefer andalso over and:

There can be situations when we do not want the second expression to be evaluated e.g.

L1 = [1, 2, 3].
L2 = [1, 2, 3, 4].
Compare = fun(X, Y) -> X =:= Y end.
Accumulate = fun(X, Acc) -> X and Acc end.
(length(L1) =:= length(L2)) andalso 
  (lists:foldl(Accumulate, true, lists:zipwith(Compare, L1, L2))).

Output:

false

Since zipwith expects both lists to be of equal length, if we use and here, the second expression (which contains the application of zipwith on two lists of unequal lengths) will also be evaluated, resulting in an exception. To get round this type of situation, we can use andalso to prevent the second expression from being evaluated.

Upvotes: 0

Kijewski
Kijewski

Reputation: 26043

Adam Lindbergs link is right. Using the comma does generate better beam code than using andalso. I compiled the following code using the +to_asm flag:

a(A,B) ->
    case ok of
        _ when A, B -> true;
        _ -> false
    end.
aa(A,B) ->
    case ok of
        _ when A andalso B -> true;
        _ -> false
    end.

which generates

{function, a, 2, 2}.
  {label,1}.
    {func_info,{atom,andAndAndalso},{atom,a},2}.
  {label,2}.
    {test,is_eq_exact,{f,3},[{x,0},{atom,true}]}.
    {test,is_eq_exact,{f,3},[{x,1},{atom,true}]}.
    {move,{atom,true},{x,0}}.
    return.
  {label,3}.
    {move,{atom,false},{x,0}}.
    return.

{function, aa, 2, 5}.
  {label,4}.
    {func_info,{atom,andAndAndalso},{atom,aa},2}.
  {label,5}.
    {test,is_atom,{f,7},[{x,0}]}.
    {select_val,{x,0},{f,7},{list,[{atom,true},{f,6},{atom,false},{f,9}]}}.
  {label,6}.
    {move,{x,1},{x,2}}.
    {jump,{f,8}}.
  {label,7}.
    {move,{x,0},{x,2}}.
  {label,8}.
    {test,is_eq_exact,{f,9},[{x,2},{atom,true}]}.
    {move,{atom,true},{x,0}}.
    return.
  {label,9}.
    {move,{atom,false},{x,0}}.
    return.

I only looked into what is generated with the +to_core flag, but obviously there is a optimization step between to_core and to_asm.

Upvotes: 8

niting112
niting112

Reputation: 2640

The boolean operators "and" and "or" always evaluate arguements on both the sides of the operator. Whereas if you want the functionality of C operators && and || (where 2nd arguement is evaluated only if needed..for eg if we want to evalue "true orelse false" as soon as true is found to be the first arguement, the second arguement will not be evaluated which is not the case had "or" been used ) go for "andalso" and "orelse".

Upvotes: 6

rvirding
rvirding

Reputation: 20926

To delve into the past:

  • Originally in guards there were only , separated tests which were evaluated from left-to-right until either there were no more and the guard succeeded or a test failed and the guard as a whole failed. Later ; was added to allow alternate guards in the same clause. If guards evaluate both sides of a , before testing then someone has gotten it wrong along the way. @Kay's example seems to imply that they do go from left-to-right as they should.

  • Boolean operators were only allowed much later in guards.

  • and, together with or, xor and not, is a boolean operator and was not intended for control. They are all strict and evaluate their arguments first, like the arithmetic operators +, -, * and '/'. There exist strict boolean operators in C as well.

  • The short-circuiting control operators andalso and orelse were added later to simplify some code. As you have said the compiler does expand them to nested case expressions so there is no performance gain in using them, just convenience and clarity of code. This would explain the resultant code you saw.

  • N.B. in guards there are tests and not expressions. There is a subtle difference which means that while using and and andalso is equivalent to , using orelse is not equivalent to ;. This is left to another question. Hint: it's all about failure.

So both and and andalso have their place.

Upvotes: 33

Adam Lindberg
Adam Lindberg

Reputation: 16587

It's an historical reason. and was implemented before andalso, which was introduced in Erlang 5.1 (the only reference I can find right now is EEP-17). Guards have not been changed because of backwards compatibility.

Upvotes: 5

Related Questions