• Aucun résultat trouvé

what the If ?!

Dans le document Learn You Some erLang for great good! (Page 75-80)

An if clause acts like a guard and shares the guard syntax, but outside a function clause’s head. In fact, if clauses are called guard patterns.

Erlang’s ifs are different from the ifs you’ll ever encounter in most other languages. Compared to those other if clauses, Erlang’s versions are weird creatures that might have been more accepted it they had a different name. When entering Erlang country, you should leave all you know about

ifs at the door.

To see how similar the if expression is to guards, enter the following examples in a module named what_the_if.erl:

-module(what_the_if).

-export([heh_fine/0]).

heh_fine() ->

if 1 =:= 1 ->

works end,

if 1 =:= 2; 1 =:= 1 ->

works end,

u if 1 =:= 2, 1 =:= 1 ->

fails end.

Save the module and let’s try it:

1> c(what_the_if).

./what_the_if.erl:12: Warning: no clause will ever match

./what_the_if.erl:12: Warning: the guard for this clause evaluates to 'false' {ok,what_the_if}

2> what_the_if:heh_fine().

** exception error: no true branch found when evaluating an if expression in function what_the_if:heh_fine/0

Uh-oh! The compiler is warning us that no clause from the if at u will ever match because its only guard evaluates to false. Remember that in Erlang, everything must return something,

and if expressions are no exception to the rule. As such, when Erlang can’t find a way to have a guard succeed, it will crash; it cannot not return something (this explains why the VM threw a “no true branch found” error when it got mad). We need to add a catchall branch that will always succeed no matter what. In most languages, this would be called an else. In Erlang, we use true, like this:

oh_god(N) ->

if N =:= 2 -> might_succeed;

true -> always_does %% This is Erlang's if's 'else!' end.

And now we can test this new function (the old one will keep spitting warnings; ignore them or take them as a reminder of what not to do):

3> c(what_the_if).

./what_the_if.erl:12: Warning: no clause will ever match

./what_the_if.erl:12: Warning: the guard for this clause evaluates to 'false' {ok,what_the_if}

4> what_the_if:oh_god(2).

might_succeed

5> what_the_if:oh_god(3).

always_does

Here’s another function that shows how to use many guards in an if

expression:

%% Note that this one would be better as a pattern match in function heads!

%% I'm doing it this way for the sake of the example.

help_me(Animal) ->

Talk = if Animal == cat -> "meow";

Animal == beef -> "mooo";

Animal == dog -> "bark";

Animal == tree -> "bark";

true -> "fgdadfgna"

end,

{Animal, "says " ++ Talk ++ "!"}.

This function also demonstrates how any expression must return something. Talk has the result of the if expression bound to it, and is then concatenated in a string, inside a tuple. When reading the code, it’s easy to see how the lack of a true branch would mess things up, considering Erlang has no such thing as a null value (such as Lisp’s nil, C’s NULL, and Python’s None).

Let’s try it:

6> c(what_the_if).

./what_the_if.erl:12: Warning: no clause will ever match

./what_the_if.erl:12: Warning: the guard for this clause evaluates to 'false' {ok,what_the_if}

7> what_the_if:help_me(dog).

{dog,"says bark!"}

8> what_the_if:help_me("it hurts!").

{"it hurts!","says fgdadfgna!"}

You might be one of the many Erlang programmers wondering why true

has taken over else as an atom to control flow—after all, else is much more familiar. Richard O’Keefe gave the following answer on the Erlang mailing lists, which I’ll quote directly because I couldn’t have put it better:

It may be more FAMILIAR, but that doesn’t mean ‘else’ is a good thing. I know that writing ‘; true ->’ is a very easy way to get ‘else’

in Erlang, but we have a couple of decades of psychology-of-programming results to show that it’s a bad idea. I have started

to replace by

if X > Y -> a() if X > Y -> a() ; true -> b() ; X =< Y -> b()

end end

if X > Y -> a() if X > Y -> a() ; X < Y -> b() ; X < Y -> b() ; true -> c() ; X ==Y -> c()

end end

which I find mildly annoying when _writing_ the code but enor-mously helpful when _reading_ it.1

In other words, else or true branches should be avoided altogether. The

if expressions are usually easier to read when you cover all logical ends, rather than relying on a catchall clause.

n o t e All this horror expressed by the function names in what_the_if.erl is in regard to the

if language construct when seen from the perspective of any other language’s if. In Erlang, it turns out to be a perfectly logical construct with a confusing name.

As mentioned earlier, only a limited set of functions can be used in guard expressions (we’ll look at more of them in Chapter 4). This is where the real conditional powers of Erlang must be conjured. I present to you . . . the case expression!

In case ... of

If the if expression is like a guard, a case ... of expression is like the whole function head. You can have the complex pattern matching available for each argument of a function, and you can have guards, too.

For this example, we’ll write the insert function for sets (a collection of unique values) that we will represent as an unordered list. This may be the

1. http://erlang.org/pipermail/erlang-questions/2009-January/041229.html

worst implementation possible in terms of efficiency, but what we want here is the syntax. Enter the following code in a file named cases.erl:

insert(X,[]) ->

[X];

insert(X,Set) ->

case lists:member(X,Set) of true -> Set;

false -> [X|Set]

end.

If we send in an empty set (list) and a term X to be added, this code returns a list containing only X. Otherwise, the function lists:member/2

checks whether an element is part of a list, and returns true if it is or false

if it is not. If we already have the element X in the set, we do not need to modify the list. Otherwise, we add X as the list’s first element.

In this case, the pattern matching is really simple. However, it can get more complex, as in this example (still in the cases module):

beach(Temperature) ->

case Temperature of

{celsius, N} when N >= 20, N =< 45 ->

'favorable';

{kelvin, N} when N >= 293, N =< 318 ->

'scientifically favorable';

{fahrenheit, N} when N >= 68, N =< 113 ->

'favorable in the US';

_ ->

'avoid beach' end.

Here, the answer to “Is it the right time to go to the beach?” is given in three different temperature systems: Celsius, Kelvin, and Fahrenheit.

Pattern matching and guards are combined in order to return an answer satisfying all uses.

As pointed out earlier, case ... of expressions are pretty much the same thing as a bunch of function heads with guards. In fact, we could have writ-ten our code the following way:

beachf({celsius, N}) when N >= 20, N =< 45 ->

'favorable';

...

beachf(_) ->

'avoid beach'.

This raises the question of whether we should use if, case ... of, or functions for conditional expressions.

Dans le document Learn You Some erLang for great good! (Page 75-80)