Why do some people shun `if`?

147 views
Skip to first unread message

Michael P.

unread,
Aug 14, 2021, 5:45:11 PM8/14/21
to Erlang mailing-list
I once stumbled over someone's Erlang style guide,
they wanted no `if`.
It was not very well explained why,
and there was no obvious e-mail address,
or I wanted to first do
some thorough thinking about it.
Now I have no more than this:

Is if not merely "syntactic sugar" for
a fun/0 with guarded clauses?
```
Now_this_is = (fun If() when Guard_seq_1 -> Expr_seq_1
%% On and on and on
; If() when Guard_seq_N -> Expr_seq_N
end)(),
% or:
Worst = case true
of true when Guard_seq_1 -> Expr_seq_1
; true when Guard_seq_2 -> Expr_seq_2
end.
```
What could be perceived as wrong with `if`?

~M

--

But who will test the tests?






zxq9

unread,
Aug 15, 2021, 12:48:57 AM8/15/21
to erlang-q...@erlang.org
Hi, Michael.

On 2021/08/15 6:44, Michael P. wrote:
> I once stumbled over someone's Erlang style guide,
> they wanted no `if`.
> It was not very well explained why,
...
> Is if not merely "syntactic sugar" for
> a fun/0 with guarded clauses?

The main reason people dislike `if` is that it applies to boolean
comparison situations where `case` provides for a much more interesting
semantic for branching. `if` is most often best used in situations where
you have a *range* of values you want to completely cover:

if
X > Y -> do_something();
X = Y -> do_something_else();
Y < Y -> do_yet_another_thing()
end

This is *not* the same as a simpler boolean case

case X > Y of
true -> foo();
false -> bar()
end

`if` enables more complex ranges and the drop-through nature of the
various clauses are occasionally very useful in reducing some evaluation
of complex checks that tend to create even messier and more complex code
to something easy to read.

You can still always do the same thing with a `case` using guards,
though -- and as you mentioned you *could* do the same with funs, but
that's a bit crazy as you're invoking the lambda machinery for really no
reason as there is motivation to create and execute a function in place
when you just want to perform a simple comparison.

In Erlang it is much more common to use `case` for just about everything
and most programs don't have a single `if` in them at all unless they
are written by people coming to Erlang from another language that only
has `if` (and then they wind up often misunderstanding the semantics of
Erlang's version of `if`, so you see the default `true` clause pop up,
which makes the newcomer think "this is silly, why does Erlang's `if`
work this way?" because using `if` that was *is* silly in most cases).

Anyway... generally speaking `case` is much more powerful than `if`, and
`if` has a very niche use case for which it is a very concise way of
doing comparisons within a function body that carries some context you
need to involve in the `if` test clauses that would otherwise be
cumbersome to pass along to a special function whose only job was to
check something in guards.

-Craig

Ulf Wiger

unread,
Aug 15, 2021, 3:36:57 AM8/15/21
to zxq9, erlang-questions
After having worked with really complex systems in very large projects, I've come to appreciate syntactic constructs signaling that nothing bad can ever happen there.

As Craig points out, 'case' is much more flexible, but this also potentially means that the expression evaluated by the case clause could contain gremlins. In an 'if' clause, only guard expressions are allowed, so only pure pattern matching - no side-effects allowed.

I think that's worth something.

BR,
Ulf

Jesper Louis Andersen

unread,
Aug 15, 2021, 9:15:53 AM8/15/21
to Michael P., Erlang mailing-list
On Sat, Aug 14, 2021 at 11:45 PM Michael P. <emp...@web.de> wrote:
I once stumbled over someone's Erlang style guide,
they wanted no `if`.


There are uses for the if...end construct in Erlang. The reason you often shun it is because it occurs quite rarely in a language where you have a proper case...end matching construct. So to avoid newcomers to fall into a trap of using if all the time, you write it down in the style guide. In reality, style can be broken for improved readability, but it takes some knowledge to know exactly when.

The deeper underlying problem is that of "boolean blindness". A value that is true/false doesn't tell you *why*. If you write a function such as

empty([]) -> true;
empty([_|_]) -> false.

you know that the list is empty, but you don't get to know it's structure. Whereas with a match

case L of
  [] -> ...;
  [H|T] -> ...
end,

you *do* know the structure in each variant clause, and you also have convenient bindings for the head and tail. If-constructs will have you analyze the structure after you hit a branch, whereas pattern matching allows you to make that analysis up front. Erlang terms are ripe for scrutiny, so as a result, you'll find much more use of case than if.

That said, and what Ulf alludes to: there are always exceptions to the rule.

zxq9

unread,
Aug 15, 2021, 9:21:05 AM8/15/21
to erlang-q...@erlang.org
On 2021/08/15 22:15, Jesper Louis Andersen wrote:
> There are uses for the if...end construct in Erlang. The reason you
> often shun it is because it occurs quite rarely in a language where you
> have a proper case...end matching construct. So to avoid newcomers to
> fall into a trap of using if all the time, you write it down in the
> style guide.
This sentence, generalized, should be in ever style guide for every
language -- and not just for `if` vs proper `case`.

I don't have time to expand on the intuition this triggered, but it is a
really great expression. Thanks, Jesper!

-Craig

Benjamin Scherrey

unread,
Aug 15, 2021, 10:11:46 AM8/15/21
to Michael P., Erlang mailing-list
Good question. It turns out that abuses of 'if' as flow control, especially synchronized flow control, is the single largest cause of defects in software development for all time. In fact, a strong case can be made (and is made in this outstanding video ( https://youtu.be/z43bmaMwagI  ) that 'goto' was the fall guy for 'if' in Dykstra's famous letter to the ACM.

Mainly the issue is the use of 'if' as a statement for alternative program control flow more than using 'if' as an expression (as is typically the case in Erlang). It generally manifests itself in imperative languages and you see it much improved in languages that provide better structural flow controls like pattern matching and aspects typical of functional languages, again like what Erlang offers.

In all my coding I restrain 'if' to assertions or guards that are either immediately passed so the program continues having passed a logic filter and never use it to decide amongst two (or more) alternative flow paths within the same code block/function.

In Erlang I only use 'if' as guard statements (and prefer 'when'). It makes me far more confident in the quality of my code when I have no 'if' statements at all.

  - - Ben Scherrey 

Richard O'Keefe

unread,
Aug 16, 2021, 3:02:05 AM8/16/21
to Michael P., Erlang mailing-list
My reason for avoiding "if" in Erlang is that it doesn't do what I expect,
so I am likely to get it wrong. From using many many other programming
languages, I expect 'if <expression>', but in Erlang it's 'if <guard>'. To
add to the confusion, instead of separating guards and expressions
even more clearly, Erlang has blurred them further. So sometimes
making that mistake will have no consequences, and other times it will
make me sorry. It's one of those things like writing
<constant> == <variable>
instead of
<variable> == <constant>
in C, to avoid the error of writing = instead of ==.

Michael Truog

unread,
Aug 16, 2021, 3:39:35 AM8/16/21
to Michael P., Erlang mailing-list
If it is possible to use an "if" expression, it can result in better
source code due to utilizing only guard expressions.  It is possible you
may need to create an extra variable to use an "if" expression but that
can help source code be more descriptive.  A "case" expression has more
complex functionality by providing matching with guard expressions and
it can result in more complex beam source code for the equivalent of an
"if" expression (though it is likely that the compiler optimizations try
to hide that when possible).

Why prefer guard expressions only?  Guard expressions are the only pure
expressions, i.e., no side-effects (if you assume node() is returning a
constant) so they help to completely isolate state, making the source
code easier to understand, use and test.  It is common for people to
avoid "if" expressions because they don't feel comfortable with them,
but it is best to use them as much as possible.

Vance Shipley

unread,
Aug 16, 2021, 4:40:14 AM8/16/21
to Erlang mailing-list
Why I find 'if' awkward is that when I test a condition for the success path and have a fall through clause for the fail path the semantics don't match with 'true'.

        if
             Foo == 42 ->
                  ok;
             true ->
                  {error, Foo}
        end.

--
     -Vance

Michael P.

unread,
Aug 17, 2021, 6:04:05 AM8/17/21
to Vance Shipley, Erlang mailing-list
I cannot identify any "falling through":
no 'C-switch-case-no-break',
not in the "true" = 'C-switch-default' or 'else'.

I think I remember having employed some
`-define(if_not, true)`
when negation-or-ing all previous conditions
seemed too "dangerous".

Michael P.

unread,
Aug 17, 2021, 9:13:00 AM8/17/21
to Michael Truog, Erlang mailing-list
On Mon, 16 Aug 2021 00:39:21 -0700
Michael Truog <mjt...@gmail.com> wrote:

> If it is possible to use an "if" expression,
> it can result in better
> source code due to utilizing only guard expressions.

Which could also be done as
a module function, a `fun`, a `case`.

And others argue that 'it can result in worse' too.


> It is possible you
> may need to create an extra variable to use an "if" expression but that
> can help source code be more descriptive.

I might always use `if` as a
match-operator expression-operand (RHS of `=`);
the name of the result telling what the decision is about.

If that is what you mean.


>  A "case" expression [...] can result in more complex beam source code

I consider this premature optimisation.

Did I not read, once upon a time, that both
`if` and `case` were compiled to `fun` anyway ...


> Why prefer guard expressions only?

But `if` is not required for "guard expressions only"?
```
f().
A = 1.
B = 2.
Max = if A >= B -> A
; A < B -> B end.
Best = case true
of true when A >= B -> A
; true when A < B -> B
end.
What_if = (fun If() when A >= B -> A
; If() when A < B -> B end)().
Now_this_is = (fun () when A >= B -> A
; () when A < B -> B end)().
```
(These are months ... years old syntax experiments:
what if I dumped `if`?
Later found out I had also re-invented Kiwi-format.)


> "if" expressions [...] it is best to use them as much as possible.

Not at the statement, not at all at you,
merely about the collision with the thread read before:
that made me laugh myself off the chair.

And it made me remember how wrong I was:
20 years ago I assumed that all such questions
had already been finally resolved in the 20 years before _then_.

~Michael

--

The name "Michael" is, more or less,
a Hebrew version of Arabic 'allahu akbar'.






Michael P.

unread,
Aug 17, 2021, 10:30:08 AM8/17/21
to erlang-q...@erlang.org

So far, I have been using `if` to tell other
programmers (and myself) that the following decision

a) is not of general use (=> module function);

b) not repeated or required by some other function (=> fun);

c) is made without patterns (=> case);

d) depends only on pure functions.

And because it is a bit more readable than `(fun () ...)()`.

Every function is a decision table,
from trivial to complex.
The pattern-matcher in my brain made me perceive
both `if` and `case` as mere subsets of `fun`.
Especially some larger `case` looks just like a `fun`,
except at the top and bottom.

Yesterday, or so, I discovered that they also, both,
lack the "(let ...)"-y nature of `fun`.

(I could have realised that half a year ago,
when Craig told me that patterns in `case`
do not introduce a new scope. But my head was
fully enganged in trying to see any sapiens in
calling an annotation an operator.)

Now, all my `if` and `case` code shifts into
the surrounding scope.

Could that be some cause of the apparent
contradiction in this thread:
'try to avoid `if`' <=/=> 'try to use `if`'?

Or is this caused by two, each good and well,
but mutually exclusive "approaches"?

I need to watch that "if = harmful" video again
that Ben pointed to, and digest it.
I need to try again to understand Jespers
excellent explanation
(I can see from tone and structure that it is,
one cannot demand one-read-no-think explanations).
And, no less, I need to reconsider all the other
hints and then stitch it all together.

Michael P.

unread,
Aug 18, 2021, 3:11:01 PM8/18/21
to zxq9, Erlang mailing-list
> On Sun, Aug 15, 2021 at 6:48 AM zxq9 <zxq9@REDACTED> wrote:
> > You can still always do the same thing with a `case` using guards,
> > though -- and as you mentioned you *could* do the same with funs, but
> > that's a bit crazy as you're invoking the lambda machinery

Have just stumbled across this
(looking for s/th else, but remembered):

"The difference between function calls and `case ... of`
are very minimal: in fact, they are represented the same way
at a lower level, and using one or the other effectively
has the same cost in terms of performance."

-- https://learnyousomeerlang.com/syntax-in-functions#in-case-of

[Reply is a bit constructed, original msg deleted,
no msg id from the archive and no clue how to put it in even if]

~M

--

  |-_|-_|-_-|            (o_o)_o)_o)_o)_o)_o)_o)_o)_o)_o)_o)_o)_o)_o)

"We're concerned / You're a threat / You're not integral to the project"




Reply all
Reply to author
Forward
0 new messages