I don’t like ifs

First of all, let me get something straight: I do not like if statements. I don’t like them in general, in any language. I could expose my reasons here, but thankfully John Degoes wrote a wonderful article about this just a few days ago. Check it out! His technique to solve the issue may be controversial (and it may only apply happily to Haskell), but I agree with the reasons he exhibits for destroying all ifs.

Now, one of the greatest things of Erlang (just joking here :P) is that I never need to explain my conceptual dislike for ifs to others. I just let them check if’s syntax and that’s usually enough to convince them not to use it. Let me show you an example…

Positively Printing

Granted, the example might not be the best one, but let’s go with it as it shows almost all issues with ifs. The idea is very simple, you want to print numbers, but you want them to display a + sign as a prefix if they’re positive. If you don’t have a deep understanding of how ifs work in Erlang (especially if you don’t come from a functional programming background), you would be tempted to write a function like this one:

1> PP = fun(X) ->

1> if X >= 0 ->

1> io:format("+")

1> end,

1> io:format("~p~n", [X])

1> end.

But then a simple test will show you that, even when it works for positive numbers it fails on negative input:

2> PP(1).

+1

ok

3> PP(-1).

** exception error: no true branch found when evaluating an if expression

What’s going on here?

This is one of those things about functional languages: Everything is an expression and therefore it needs to evaluate to some value. So, the expression if X > 0 -> … end must have a value for every possible value of X if you want Erlang to evaluate your function. The function as it is written above doesn’t provide a way to evaluate the if expression for negative values of X. So, if you call the function with a negative value, you get an if_clause exception. In other words, unless you’re really sure no one is going to call you function with negative values, you need to provide a true branch for that if.

A true branch works like an else in other languages, although since if in Erlang is a multi-clause expression built with guards and bodies there is no keyword for that. You just have to provide a guard that is always true to achieve the desired effect. There is even an example of that in the docs. Let’s upgrade our own example…

5> PP = fun(X) ->

5> if X >= 0 ->

5> io:format("+")

5> ; true ->

5> do_nothing

5> end,

5> io:format("~p~n", [X])

5> end.

Let’s test our new function…

6> PP(1).

+1

ok

7> PP(-1).

-1

ok

And sure enough, it worked!

But I don’t want true branches!

More often than not, adding a true branch to every if is not something that the developer who wrote the else-less if in the first place wants to do. Facing that dilemma, some developers learn to achieve the same result in a more functional way, or else…

9> PP = fun(X) ->

9> X >= 0 andalso io:format("+"),

9> io:format("~p~n", [X])

9> end.

#Fun<erl_eval.6.52032458>

Yes! Using the short-circuit boolean operator andalso, they can achieve the else-less if effect. Check it out!

10> PP(1).

+1

ok

11> PP(-1).

-1

ok

What if…?

Amazing, right? And you can even circumvent the restrictions imposed by Erlang ifs because their clauses are guards and not expressions. You can use any expression you want before the boolean operator. You can write code like this inside your functions:

…

is_registered(User)

andalso register_login(User, dates:now()),

…

But then… What if you need more than one condition?

is_registered(User)

andalso password_matches(User, Password)

andalso

register_login(User, dates:now()),

What if the conditions are a little bit more complicated than that?

is_registered(User)

andalso password_matches(User, Password)

andalso (has_active_session(User) orelse is_admin(User))

andalso

register_login(User, dates:now()),

What if you need to do more than just one thing if the conditions match?

is_registered(User)

andalso password_matches(User, Password)

andalso (has_active_session(User) orelse is_admin(User))

andalso

begin

register_login(User, dates:now()),

open_session(User)

end

What if you have nested if statements?

is_registered(User)

andalso password_matches(User, Password)

andalso (has_active_session(User) orelse is_admin(User))

andalso

begin

register_login(User, dates:now()),

is_admin(User) andalso open_admin_session(User)

end

Actually… you can use a trick we learned about a week ago, right?

is_registered(User)

andalso password_matches(User, Password)

andalso (has_active_session(User) orelse is_admin(User))

andalso

begin

register_login(User, dates:now()),

[ open_admin_session(User) || is_admin(User) ]

end

If this extremely ugly code doesn’t convince you not to use this method, I don’t know what else I can do…