SE 765 - Distributed Software Development

CS 610 - Introduction to Parallel and Distributed Computing

Erlang 2 – Basic Programming, Part 2

 

This lecture uses materials and has been adapted from:

http://www.ibm.com/developerworks/opensource/library/os-erlang1/

http://www.erlang.org/doc/pdf/otp-system-documentation.pdf

http://www.claystuart.com/

Term Comparisons

Expr1 op Expr2

op

Description

==

equal to

/=

not equal to

=<

less than or equal to

< 

less than

>=

greater than or equal to

> 

greater than

=:=

exactly equal to

=/=

exactly not equal to

Term Comparison Operators.

The arguments may be of different data types. The following order is defined:

number < atom < reference < fun < port < pid < tuple < list < bit string

Lists are compared element by element. Tuples are ordered by size, two tuples with the same size are compared element by element.

When comparing an integer to a float, the term with the lesser precision will be converted into the other term's type, unless the operator is one of =:= or =/=. A float is more precise than an integer until all significant figures of the float are to the left of the decimal point. This happens when the float is larger/smaller than +/-9007199254740992.0. The conversion strategy is changed depending on the size of the float because otherwise comparison of large floats and integers would lose their transitivity.

Returns the Boolean value of the expression, true or false.

Examples:

1> 1==1.0.

true

2> 1=:=1.0.

false

3> 1 > a.

false

Arithmetic Expressions

op Expr

Expr1 op Expr2

op

Description

Argument type

+

unary +

number

-

unary -

number

+

 

number

-

 

number

*

 

number

/

floating point division

number

bnot

unary bitwise not

integer

div

integer division

integer

rem

integer remainder of X/Y

integer

band

bitwise and

integer

bor

bitwise or

integer

bxor

arithmetic bitwise xor

integer

bsl

arithmetic bitshift left

integer

bsr

bitshift right

integer

Table 7.2:   Arithmetic Operators.

Examples:

1> +1.

1

2> -1.

-1

3> 1+1.

2

4> 4/2.

2.0

5> 5 div 2.

2

6> 5 rem 2.

1

7> 2#10 band 2#01.

0

8> 2#10 bor 2#01.

3

9> a + 10.

** exception error: an error occurred when evaluating an arithmetic expression

     in operator  +/2

        called as a + 10

10> 1 bsl (1 bsl 64).

** exception error: a system limit has been reached

     in operator  bsl/2

        called as 1 bsl 18446744073709551616

Boolean Expressions

op Expr

Expr1 op Expr2

op

Description

not

unary logical not

and

logical and

or

logical or

xor

logical xor

Logical Operators.

Examples:

1> not true.

false

2> true and false.

false

3> true xor false.

true

4> true or garbage.

** exception error: bad argument

     in operator  or/2

        called as true or garbage

 

A Larger Example

·         Assume we have a list of temperature readings from a number of cities in the world.

·         Some of them are in Celsius (Centigrade) and some in Fahrenheit .

·         First let's convert them all to Celsius, then let's print out the data neatly.

%% This module is in file tut5.erl

-module(tut5).

-export([format_temps/1]).

%% Only this function is exported

 

format_temps([])->                        % No output for an empty list

    ok;

format_temps([City | Rest]) ->

    print_temp(convert_to_celsius(City)),

    format_temps(Rest).

 

convert_to_celsius({Name, {c, Temp}}) ->  % No conversion needed

    {Name, {c, Temp}};

convert_to_celsius({Name, {f, Temp}}) ->  % Do the conversion

    {Name, {c, (Temp - 32) * 5 / 9}}.

 

print_temp({Name, {c, Temp}}) ->

    io:format("~-15w ~w c~n", [Name, Temp]).

 

35> c(tut5).

{ok,tut5}

36> tut5:format_temps([{moscow, {c, -10}}, {cape_town, {f, 70}},

{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).

moscow          -10 c

cape_town       21.11111111111111 c

stockholm       -4 c

paris           -2.2222222222222223 c

london          2.2222222222222223 c

ok

·         Notice comments to the code.

o   A comment starts with a % character and goes on to the end of the line.

·         Note as well that the -export([format_temps/1]). line only includes the function format_temps/1,

o   the other functions are local functions, i.e. they are not visible from outside the module tut5.

·         Note when testing the program from the shell, the input was spread over two lines as the line was too long.

 

·         When format_temps is called the first time, City gets the value {moscow,{c,-10}}

·         and Rest is the rest of the list.

·         So print_temp(convert_to_celsius({moscow,{c,-10}})) is called.

 

·         The function convert_to_celsius({moscow,{c,-10}}) is called as the argument to the function print_temp.

·         When function are nested, they are executed (evaluated) from the inside out.

o   i.e. first evaluate convert_to_celsius({moscow,{c,-10}}) which gives the value {moscow,{c,-10}} as the temperature is already in Celsius

o   then evaluate print_temp({moscow,{c,-10}}).

·         print_temp simply calls io:format.

o   Note that ~-15w says to print the "term" with a field length (width) of 15 and left justify it. (io(3)).

 

·         Now we call format_temps(Rest) with the rest of the list as an argument.

·         This way of doing things is similar to the loop constructs in other languages.

·         So the same format_temps function is called again, this time City gets the value {cape_town,{f,70}} and we repeat the same procedure as before. We go on doing this until the list becomes empty, i.e. [], which causes the first clause format_temps([]) to match.

·         This simply returns (results in) the atom ok, so the program ends.

 

Matching, Guards and Scope of Variables

It could be useful to find the maximum and minimum temperature in lists like this. Before extending the program to do this, let's look at functions for finding the maximum value of the elements in a list:

-module(tut6).

-export([list_max/1]).

 

list_max([Head|Rest]) ->

   list_max(Rest, Head).

 

list_max([], Res) ->

    Res;

list_max([Head|Rest], Result_so_far) when Head > Result_so_far ->

    list_max(Rest, Head);

list_max([Head|Rest], Result_so_far)  ->

    list_max(Rest, Result_so_far).

 

37> c(tut6).

{ok,tut6}

38> tut6:list_max([1,2,3,4,5,7,4,3,2,1]).

7

·         Note: two functions with the same name list_max.

·         Each takes a different number of arguments (parameters).

·         In Erlang these are regarded as completely different functions.

·         To distinguish between these functions we write name/arity, where name is the name of the function and arity is the number of arguments, in this case list_max/1 and list_max/2.

Example of walking through a list "carrying" a value:

·         Here Result_so_far. list_max/1 assumes that the max value of the list is the head of the list and calls list_max/2 with the rest of the list

o   the value of the head of the list, in the above this would be list_max([2,3,4,5,7,4,3,2,1],1).

·         If we tried to use list_max/1 with an empty list or tried to use it with something which isn't a list at all, we would cause an error.

 

·         In list_max/2 we walk down the list and use Head instead of Result_so_far when Head > Result_so_far.

·         when is a special word used before the -> in the function to say that we should only use this part of the function if the test which follows is true.

o   We call tests of this type a guard.

o   If the guard isn't true (the guard fails), and try the next part of the function.

§  In this case if Head isn't greater than Result_so_far then it must be smaller or equal to is, so we don't need a guard on the next part of the function.

 

·         Some useful operators in guards are, < less than, > greater than, == equal, >= greater or equal, =< less or equal, /= not equal.

·         To change the above program to one which works out the minimum value of the element in a list, write < instead of >.

o   (But it would be wise to change the name of the function to list_min :-).

 

More About Lists

Remember that the | operator can be used to get the head of a list:

47> [M1|T1] = [paris, london, rome].

[paris,london,rome]

48> M1.

paris

49> T1.

[london,rome]

The | operator can also be used to add a head to a list:

50> L1 = [madrid | T1].

[madrid,london,rome]

51> L1.

[madrid,london,rome]

Example - reversing the order of a list:

-module(tut8).

 

-export([reverse/1]).

 

reverse(List) ->

    reverse(List, []).

 

reverse([Head | Rest], Reversed_List) ->

    reverse(Rest, [Head | Reversed_List]);

 

reverse([], Reversed_List) ->

    Reversed_List.

 

52> c(tut8).

{ok,tut8}

53> tut8:reverse([1,2,3]).

[3,2,1]

Consider how Reversed_List is built:

It starts as [], then successively takes off the heads of the list to be reversed and add them to the Reversed_List, as shown in the following:

reverse([1|2,3], []) =>

    reverse([2,3], [1|[]])

 

reverse([2|3], [1]) =>

    reverse([3], [2|[1])

 

reverse([3|[]], [2,1]) =>

    reverse([], [3|[2,1]])

 

reverse([], [3,2,1]) =>

    [3,2,1]

Erlang’s lists module contains many functions for manipulating lists, e.g. reversing them, so before you write a list manipulating function it is a good idea to check that one isn't already written for you. (see lists(3)).

 

Cities and Temperatures example reconsidered.

First, convert the entire list to Celsius as follows and test the function:

-module(tut7).

-export([format_temps/1]).

 

format_temps(List_of_cities) ->

    convert_list_to_c(List_of_cities).

 

convert_list_to_c([{Name, {f, F}} | Rest]) ->

    Converted_City = {Name, {c, (F -32)* 5 / 9}},

    [Converted_City | convert_list_to_c(Rest)];

 

convert_list_to_c([City | Rest]) ->

    [City | convert_list_to_c(Rest)];

 

convert_list_to_c([]) ->

    [].

 

54> c(tut7).

{ok, tut7}.

55> tut7:format_temps([{moscow, {c, -10}}, {cape_town, {f, 70}},

{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).

[{moscow,{c,-10}},

 {cape_town,{c,21.11111111111111}},

 {stockholm,{c,-4}},

 {paris,{c,-2.2222222222222223}},

 {london,{c,2.2222222222222223}}]

Looking at this bit by bit:

format_temps(List_of_cities) ->

    convert_list_to_c(List_of_cities).

·         Here format_temps/1 calls convert_list_to_c/1. convert_list_to_c/1 takes off the head of the List_of_cities, converts it to Celsius if needed.

·         The | operator is used to add the (maybe) converted to the converted rest of the list:

[Converted_City | convert_list_to_c(Rest)];

or

[City | convert_list_to_c(Rest)];

This is done until we get to the end of the list (i.e. the list is empty:

convert_list_to_c([]) ->

    [].

Now we have converted the list, we add a function to print it:

-module(tut7).

-export([format_temps/1]).

 

format_temps(List_of_cities) ->

    Converted_List = convert_list_to_c(List_of_cities),

    print_temp(Converted_List).

 

convert_list_to_c([{Name, {f, F}} | Rest]) ->

    Converted_City = {Name, {c, (F -32)* 5 / 9}},

    [Converted_City | convert_list_to_c(Rest)];

 

convert_list_to_c([City | Rest]) ->

    [City | convert_list_to_c(Rest)];

 

convert_list_to_c([]) ->

    [].

 

print_temp([{Name, {c, Temp}} | Rest]) ->

    io:format("~-15w ~w c~n", [Name, Temp]),

    print_temp(Rest);

print_temp([]) ->

    ok.

 

56> c(tut7).

{ok,tut7}

57> tut7:format_temps([{moscow, {c, -10}}, {cape_town, {f, 70}},

{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).

moscow          -10 c

cape_town       21.11111111111111 c

stockholm       -4 c

paris           -2.2222222222222223 c

london          2.2222222222222223 c

ok

Now:  add a function to find the cities with the maximum and minimum temperatures.

-module(tut7).

-export([format_temps/1]).

 

format_temps(List_of_cities) ->

    Converted_List = convert_list_to_c(List_of_cities),

    print_temp(Converted_List),

    {Max_city, Min_city} = find_max_and_min(Converted_List),

    print_max_and_min(Max_city, Min_city).

 

convert_list_to_c([{Name, {f, Temp}} | Rest]) ->

    Converted_City = {Name, {c, (Temp -32)* 5 / 9}},

    [Converted_City | convert_list_to_c(Rest)];

 

convert_list_to_c([City | Rest]) ->

    [City | convert_list_to_c(Rest)];

 

convert_list_to_c([]) ->

    [].

 

print_temp([{Name, {c, Temp}} | Rest]) ->

    io:format("~-15w ~w c~n", [Name, Temp]),

    print_temp(Rest);

print_temp([]) ->

    ok.

 

find_max_and_min([City | Rest]) ->

    find_max_and_min(Rest, City, City).

 

find_max_and_min([{Name, {c, Temp}} | Rest],

         {Max_Name, {c, Max_Temp}},

         {Min_Name, {c, Min_Temp}}) ->

    if

        Temp > Max_Temp ->

            Max_City = {Name, {c, Temp}};           % Change

        true ->

            Max_City = {Max_Name, {c, Max_Temp}} % Unchanged

    end,

    if

         Temp < Min_Temp ->

            Min_City = {Name, {c, Temp}};           % Change

        true ->

            Min_City = {Min_Name, {c, Min_Temp}} % Unchanged

    end,

    find_max_and_min(Rest, Max_City, Min_City);

 

find_max_and_min([], Max_City, Min_City) ->

    {Max_City, Min_City}.

 

print_max_and_min({Max_name, {c, Max_temp}}, {Min_name, {c, Min_temp}}) ->

    io:format("Max temperature was ~w c in ~w~n", [Max_temp, Max_name]),

    io:format("Min temperature was ~w c in ~w~n", [Min_temp, Min_name]).

 

58> c(tut7).

{ok, tut7}

59> tut7:format_temps([{moscow, {c, -10}}, {cape_town, {f, 70}},

{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).

moscow          -10 c

cape_town       21.11111111111111 c

stockholm       -4 c

paris           -2.2222222222222223 c

london          2.2222222222222223 c

Max temperature was 21.11111111111111 c in cape_town

Min temperature was -10 c in moscow

ok

 

If and Case

·         The function find_max_and_min works out the maximum and minimum temperature.

·         We have introduced a new construct here if.

·         If works as follows:

if

    Condition 1 ->

        Action 1;

    Condition 2 ->

        Action 2;

    Condition 3 ->

        Action 3;

    Condition 4 ->

        Action 4

end

·         Note there is no ";" before end!

·         Conditions are the same as guards, tests which succeed or fail.

·         Erlang starts at the top until it finds a condition which succeeds and then it evaluates (performs) the action following the condition and ignores all other conditions and action before the end.

·         If no condition matches, there will be a run-time failure.

·         A condition which always is succeeds is the atom, true and this is often used last in an if meaning do the action following the true if all other conditions have failed.

The following is a short program to show the workings of if.

-module(tut9).

-export([test_if/2]).

 

test_if(A, B) ->

    if

        A == 5 ->

            io:format("A == 5~n", []),

            a_equals_5;

        B == 6 ->

            io:format("B == 6~n", []),

            b_equals_6;

        A == 2, B == 3 ->                      %i.e. A equals 2 and B equals 3

            io:format("A == 2, B == 3~n", []),

            a_equals_2_b_equals_3;

        A == 1 ; B == 7 ->                     %i.e. A equals 1 or B equals 7

            io:format("A == 1 ; B == 7~n", []),

            a_equals_1_or_b_equals_7

    end.

Testing this program gives:

60> c(tut9).

{ok,tut9}

61> tut9:test_if(5,33).

A == 5

a_equals_5

62> tut9:test_if(33,6).

B == 6

b_equals_6

63> tut9:test_if(2, 3).

A == 2, B == 3

a_equals_2_b_equals_3

64> tut9:test_if(1, 33).

A == 1 ; B == 7

a_equals_1_or_b_equals_7

65> tut9:test_if(33, 7).

A == 1 ; B == 7

a_equals_1_or_b_equals_7

66> tut9:test_if(33, 33).

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

     in function  tut9:test_if/2 (tut9.erl, line 5)

Note tut9:test_if(33,33) did not cause any condition to succeed so we got the run time error if_clause.

Recall that convert_length function was written as:

convert_length({centimeter, X}) ->

    {inch, X / 2.54};

convert_length({inch, Y}) ->

    {centimeter, Y * 2.54}.

The same program could be written as:

-module(tut10).

-export([convert_length/1]).

 

convert_length(Length) ->

    case Length of

        {centimeter, X} ->

            {inch, X / 2.54};

        {inch, Y} ->

            {centimeter, Y * 2.54}

    end.

 

67> c(tut10).

{ok,tut10}

68> tut10:convert_length({inch, 6}).

{centimeter,15.24}

69> tut10:convert_length({centimeter, 2.5}).

{inch,0.984251968503937}

Note: both case and if have return values, i.e. in the above example case returned either {inch,X/2.54} or {centimeter,Y*2.54}.

·         The behavior of case can also be modified by using guards.

·         The following example tells us the length of a month, given the year.

o   We need to know the year, since February has 29 days in a leap year.

-module(tut11).

-export([month_length/2]).

 

month_length(Year, Month) ->

    %% All years divisible by 400 are leap

    %% Years divisible by 100 are not leap (except the 400 rule above)

    %% Years divisible by 4 are leap (except the 100 rule above)

    Leap = if

        trunc(Year / 400) * 400 == Year ->

            leap;

        trunc(Year / 100) * 100 == Year ->

            not_leap;

        trunc(Year / 4) * 4 == Year ->

            leap;

        true ->

            not_leap

    end, 

    case Month of

        sep -> 30;

        apr -> 30;

        jun -> 30;

        nov -> 30;

        feb when Leap == leap -> 29;

        feb -> 28;

        jan -> 31;

        mar -> 31;

        may -> 31;

        jul -> 31;

        aug -> 31;

        oct -> 31;

        dec -> 31

    end.

 

70> c(tut11).

{ok,tut11}

71> tut11:month_length(2004, feb).

29

72> tut11:month_length(2003, feb).

28

73> tut11:month_length(1947, aug).

31

 

Higher Order Functions (Funs)

Erlang has higher order functions.

86> Xf = fun(X) -> X * 2 end.

#Fun<erl_eval.5.123085357>

87> Xf(5).

10

Here, a function is defined which doubles the value of number and assigns this function to a variable.

The variable Xf(5) returned the value 10.

Two useful functions when working with lists are foreach and map, which are defined as follows:

foreach(Fun, [First|Rest]) ->

    Fun(First),

    foreach(Fun, Rest);

foreach(Fun, []) ->

    ok.

 

map(Fun, [First|Rest]) ->

    [Fun(First)|map(Fun,Rest)];

map(Fun, []) ->

    [].

o   These two functions are provided in the standard module lists.

o   foreach takes a list and applies a fun to every element in the list

o   map creates a new list by applying a fun to every element in a list.

Here  map and a fun are used to add 3 to every element of a list:

88> Add_3 = fun(X) -> X + 3 end.

#Fun<erl_eval.5.123085357>

89> lists:map(Add_3, [1,2,3]).

[4,5,6]

 

Now print out the temperatures in a list of cities (yet again):

90> Print_City = fun({City, {X, Temp}}) -> io:format("~-15w ~w ~w~n",

[City, X, Temp]) end.

#Fun<erl_eval.5.123085357>

91> lists:foreach(Print_City, [{moscow, {c, -10}}, {cape_town, {f, 70}},

{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).

moscow          c -10

cape_town       f 70

stockholm       c -4

paris           f 28

london          f 36

ok

 

Now define a fun which can be used to go through a list of cities and temperatures and transform them all to Celsius.

-module(tut13).

 

-export([convert_list_to_c/1]).

 

convert_to_c({Name, {f, Temp}}) ->

    {Name, {c, trunc((Temp - 32) * 5 / 9)}};

convert_to_c({Name, {c, Temp}}) ->

    {Name, {c, Temp}}.

 

convert_list_to_c(List) ->

    lists:map(fun convert_to_c/1, List).

 

92> tut13:convert_list_to_c([{moscow, {c, -10}}, {cape_town, {f, 70}},

{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).

[{moscow,{c,-10}},

 {cape_town,{c,21}},

 {stockholm,{c,-4}},

 {paris,{c,-2}},

 {london,{c,2}}]

The convert_to_c function is the same as before, but will be used as a fun:

lists:map(fun convert_to_c/1, List)

o   When a function is defined elsewhere and used as a fun it can be referred to as Function/Arity (remember that Arity = number of arguments).

o   For the  map call becomes lists:map(fun convert_to_c/1, List).

o   convert_list_to_c becomes much shorter and easier to understand.

 

o   The standard module lists also contains a function sort(Fun, List) where Fun is a fun with two arguments.

o   This fun should return true if the the first argument is less than the second argument, or else false.

o   Sortingis added  to the convert_list_to_c:

-module(tut13).

 

-export([convert_list_to_c/1]).

 

convert_to_c({Name, {f, Temp}}) ->

    {Name, {c, trunc((Temp - 32) * 5 / 9)}};

convert_to_c({Name, {c, Temp}}) ->

    {Name, {c, Temp}}.

 

convert_list_to_c(List) ->

    New_list = lists:map(fun convert_to_c/1, List),

    lists:sort(fun({_, {c, Temp1}}, {_, {c, Temp2}}) ->

                       Temp1 < Temp2 end, New_list).

93> c(tut13).

{ok,tut13}

94> tut13:convert_list_to_c([{moscow, {c, -10}}, {cape_town, {f, 70}},

{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).

[{moscow,{c,-10}},

 {stockholm,{c,-4}},

 {paris,{c,-2}},

 {london,{c,2}},

 {cape_town,{c,21}}]

In sort the fun:

fun({_, {c, Temp1}}, {_, {c, Temp2}}) -> Temp1 < Temp2 end,

o   Here the concept of an anonymous variable "_" is introduced

o   This is simply shorthand for a variable which is going to get a value, but we will ignore the value.

o   This can be used anywhere suitable, not just in fun's. Temp1 < Temp2 returns true if Temp1 is less than Temp2.