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
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 |
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 |
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 |
·
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.
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 :-).
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
·
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
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.