2 Sequential Programming
2.1 The Erlang Shell
Most operating systems have a command interpreter or shell, Unix and Linux have many, Windows has the Command Prompt. Erlang has its own shell where you can directly write bits of Erlang code and evaluate (run) them to see what happens (see shell(3)). Start the Erlang shell (in Linux or UNIX) by starting a shell or command interpreter in your operating system and typing erl, you will see something like this.
% erl
Erlang R15B (erts-5.9.1) [source] [smp:8:8] [rq:8] [async-threads:0] [hipe] [kernel-poll:false]
Eshell V5.9.1 (abort with ^G)
1>
Now type in "2 + 5." as shown below.
1> 2 + 5.
7
2>
In Windows, the shell is started by double-clicking on the Erlang shell icon.
You'll notice that the Erlang shell has numbered the lines that can be entered, (as 1> 2>) and that it has correctly told you that 2 + 5 is 7! Also notice that you have to tell it you are done entering code by finishing with a full stop "." and a carriage return. If you make mistakes writing things in the shell, you can delete things by using the backspace key as in most shells. There are many more editing commands in the shell (See the chapter "tty - A command line interface" in ERTS User's Guide).
(Note: you will find a lot of line numbers given by the shell out of sequence in this tutorial as it was written and the code tested in several sessions).
Now let's try a more complex calculation.
2> (42 + 77) * 66 / 3.
2618.0
Here you can see the use of brackets and the multiplication operator "*" and division operator "/", just as in normal arithmetic (see the chapter "Arithmetic Expressions" in the Erlang Reference Manual).
To shutdown the Erlang system and the Erlang shell type Control-C. You will see the following output:
BREAK: (a)bort (c)ontinue (p)roc info (i)nfo (l)oaded
(v)ersion (k)ill (D)b-tables (d)istribution
a
%
Type "a" to leave the Erlang system.
Another way to shutdown the Erlang system is by entering halt():
3> halt().
%
2.2 Modules and Functions
A programming language isn't much use if you can just run code from the shell. So here is a small Erlang program. Enter it into a file called tut.erl (the file name tut.erl is important, also make sure that it is in the same directory as the one where you started erl) using a suitable text editor. If you are lucky your editor will have an Erlang mode which will make it easier for you to enter and format your code nicely (see the chapter "The Erlang mode for Emacs" in Tools User's Guide), but you can manage perfectly well without. Here's the code to enter:
-module(tut). -export([double/1]). double(X) -> 2 * X.
It's not hard to guess that this "program" doubles the value of numbers. I'll get back to the first two lines later. Let's compile the program. This can be done in your Erlang shell as shown below:
3> c(tut).
{ok,tut}
The {ok,tut} tells you that the compilation was OK. If it said "error" instead, you have made some mistake in the text you entered and there will also be error messages to give you some idea as to what has gone wrong so you can change what you have written and try again.
Now lets run the program.
4> tut:double(10).
20
As expected double of 10 is 20.
Now let's get back to the first two lines. Erlang programs are written in files. Each file contains what we call an Erlang module. The first line of code in the module tells us the name of the module (see the chapter "Modules" in the Erlang Reference Manual).
-module(tut).
This tells us that the module is called tut. Note the "." at the end of the line. The files which are used to store the module must have the same name as the module but with the extension ".erl". In our case the file name is tut.erl. When we use a function in another module, we use the syntax, module_name:function_name(arguments). So
4> tut:double(10).
means call function double in module tut with argument "10".
The second line:
-export([double/1]).
says that the module tut contains a function called double which takes one argument (X in our example) and that this function can be called from outside the module tut. More about this later. Again note the "." at the end of the line.
Now for a more complicated example, the factorial of a number (e.g. factorial of 4 is 4 * 3 * 2 * 1). Enter the following code in a file called tut1.erl.
-module(tut1). -export([fac/1]). fac(1) -> 1; fac(N) -> N * fac(N - 1).
Compile the file
5> c(tut1).
{ok,tut1}
And now calculate the factorial of 4.
6> tut1:fac(4).
24
The first part:
fac(1) -> 1;
says that the factorial of 1 is 1. Note that we end this part with a ";" which indicates that there is more of this function to come. The second part:
fac(N) -> N * fac(N - 1).
says that the factorial of N is N multiplied by the factorial of N - 1. Note that this part ends with a "." saying that there are no more parts of this function.
A function can have many arguments. Let's expand the module tut1 with the rather stupid function to multiply two numbers:
-module(tut1). -export([fac/1, mult/2]). fac(1) -> 1; fac(N) -> N * fac(N - 1). mult(X, Y) -> X * Y.
Note that we have also had to expand the -export line with the information that there is another function mult with two arguments.
Compile:
7> c(tut1).
{ok,tut1}
and try it out:
8> tut1:mult(3,4).
12
In the example above the numbers are integers and the arguments in the functions in the code, N, X, Y are called variables. Variables must start with a capital letter (see the chapter "Variables" in the Erlang Reference Manual). Examples of variable could be Number, ShoeSize, Age etc.
2.3 Atoms
Atoms are another data type in Erlang. Atoms start with a small letter ((see the chapter "Atom" in the Erlang Reference Manual)), for example: charles, centimeter, inch. Atoms are simply names, nothing else. They are not like variables which can have a value.
Enter the next program (file: tut2.erl) which could be useful for converting from inches to centimeters and vice versa:
-module(tut2). -export([convert/2]). convert(M, inch) -> M / 2.54; convert(N, centimeter) -> N * 2.54.
Compile and test:
9> c(tut2). {ok,tut2} 10> tut2:convert(3, inch). 1.1811023622047243 11> tut2:convert(7, centimeter). 17.78
Notice that I have introduced decimals (floating point numbers) without any explanation, but I guess you can cope with that.
See what happens if I enter something other than centimeter or inch in the convert function:
12> tut2:convert(3, miles).
** exception error: no function clause matching tut2:convert(3,miles) (tut2.erl, line 4)
The two parts of the convert function are called its clauses. Here we see that "miles" is not part of either of the clauses. The Erlang system can't match either of the clauses so we get an error message function_clause. The shell formats the error message nicely, but the error tuple is saved in the shell's history list and can be output by the shell command v/1:
13> v(12).
{'EXIT',{function_clause,[{tut2,convert,
[3,miles],
[{file,"tut2.erl"},{line,4}]},
{erl_eval,do_apply,5,[{file,"erl_eval.erl"},{line,482}]},
{shell,exprs,7,[{file,"shell.erl"},{line,666}]},
{shell,eval_exprs,7,[{file,"shell.erl"},{line,621}]},
{shell,eval_loop,3,[{file,"shell.erl"},{line,606}]}]}}
2.4 Tuples
Now the tut2 program is hardly good programming style. Consider:
tut2:convert(3, inch).
Does this mean that 3 is in inches? or that 3 is in centimeters and we want to convert it to inches? So Erlang has a way to group things together to make things more understandable. We call these tuples. Tuples are surrounded by "{" and "}".
So we can write {inch,3} to denote 3 inches and {centimeter,5} to denote 5 centimeters. Now let's write a new program which converts centimeters to inches and vice versa. (file tut3.erl).
-module(tut3). -export([convert_length/1]). convert_length({centimeter, X}) -> {inch, X / 2.54}; convert_length({inch, Y}) -> {centimeter, Y * 2.54}.
Compile and test:
14> c(tut3). {ok,tut3} 15> tut3:convert_length({inch, 5}). {centimeter,12.7} 16> tut3:convert_length(tut3:convert_length({inch, 5})). {inch,5.0}
Note on line 16 we convert 5 inches to centimeters and back again and reassuringly get back to the original value. I.e the argument to a function can be the result of another function. Pause for a moment and consider how line 16 (above) works. The argument we have given the function {inch,5} is first matched against the first head clause of convert_length i.e. convert_length({centimeter,X}) where it can be seen that {centimeter,X} does not match {inch,5} (the head is the bit before the "->"). This having failed, we try the head of the next clause i.e. convert_length({inch,Y}), this matches and Y get the value 5.
We have shown tuples with two parts above, but tuples can have as many parts as we want and contain any valid Erlang term. For example, to represent the temperature of various cities of the world we could write
{moscow, {c, -10}} {cape_town, {f, 70}} {paris, {f, 28}}
Tuples have a fixed number of things in them. We call each thing in a tuple an element. So in the tuple {moscow,{c,-10}}, element 1 is moscow and element 2 is {c,-10}. I have chosen c meaning Centigrade (or Celsius) and f meaning Fahrenheit.
2.5 Lists
Whereas tuples group things together, we also want to be able to represent lists of things. Lists in Erlang are surrounded by "[" and "]". For example a list of the temperatures of various cities in the world could be:
[{moscow, {c, -10}}, {cape_town, {f, 70}}, {stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]
Note that this list was so long that it didn't fit on one line. This doesn't matter, Erlang allows line breaks at all "sensible places" but not, for example, in the middle of atoms, integers etc.
A very useful way of looking at parts of lists, is by using "|". This is best explained by an example using the shell.
17> [First |TheRest] = [1,2,3,4,5]. [1,2,3,4,5] 18> First. 1 19> TheRest. [2,3,4,5]
We use | to separate the first elements of the list from the rest of the list. (First has got value 1 and TheRest value [2,3,4,5]).
Another example:
20> [E1, E2 | R] = [1,2,3,4,5,6,7]. [1,2,3,4,5,6,7] 21> E1. 1 22> E2. 2 23> R. [3,4,5,6,7]
Here we see the use of | to get the first two elements from the list. Of course if we try to get more elements from the list than there are elements in the list we will get an error. Note also the special case of the list with no elements [].
24> [A, B | C] = [1, 2]. [1,2] 25> A. 1 26> B. 2 27> C. []
In all the examples above, I have been using new variable names, not reusing the old ones: First, TheRest, E1, E2, R, A, B, C. The reason for this is that a variable can only be given a value once in its context (scope). I'll get back to this later, it isn't so peculiar as it sounds!
The following example shows how we find the length of a list:
-module(tut4). -export([list_length/1]). list_length([]) -> 0; list_length([First | Rest]) -> 1 + list_length(Rest).
Compile (file tut4.erl) and test:
28> c(tut4). {ok,tut4} 29> tut4:list_length([1,2,3,4,5,6,7]). 7
Explanation:
list_length([]) -> 0;
The length of an empty list is obviously 0.
list_length([First | Rest]) -> 1 + list_length(Rest).
The length of a list with the first element First and the remaining elements Rest is 1 + the length of Rest.
(Advanced readers only: This is not tail recursive, there is a better way to write this function).
In general we can say we use tuples where we would use "records" or "structs" in other languages and we use lists when we want to represent things which have varying sizes, (i.e. where we would use linked lists in other languages).
Erlang does not have a string data type, instead strings can be represented by lists of ASCII characters. So the list [97,98,99] is equivalent to "abc". The Erlang shell is "clever" and guesses the what sort of list we mean and outputs it in what it thinks is the most appropriate form, for example:
30> [97,98,99].
"abc"
2.6 Maps
Maps are a set of key to value associations. These associations are encapsulated with "#{" and "}". To create an association from "key" to value 42, we write:
> #{ "key" => 42 }. #{"key" => 42}
We will jump straight into the deep end with an example using some interesting features.
The following example shows how we calculate alpha blending using maps to reference color and alpha channels:
-module(color). -export([new/4, blend/2]). -define(is_channel(V), (is_float(V) andalso V >= 0.0 andalso V =< 1.0)). new(R,G,B,A) when ?is_channel(R), ?is_channel(G), ?is_channel(B), ?is_channel(A) -> #{red => R, green => G, blue => B, alpha => A}. blend(Src,Dst) -> blend(Src,Dst,alpha(Src,Dst)). blend(Src,Dst,Alpha) when Alpha > 0.0 -> Dst#{ red := red(Src,Dst) / Alpha, green := green(Src,Dst) / Alpha, blue := blue(Src,Dst) / Alpha, alpha := Alpha }; blend(_,Dst,_) -> Dst#{ red := 0.0, green := 0.0, blue := 0.0, alpha := 0.0 }. alpha(#{alpha := SA}, #{alpha := DA}) -> SA + DA*(1.0 - SA). red(#{red := SV, alpha := SA}, #{red := DV, alpha := DA}) -> SV*SA + DV*DA*(1.0 - SA). green(#{green := SV, alpha := SA}, #{green := DV, alpha := DA}) -> SV*SA + DV*DA*(1.0 - SA). blue(#{blue := SV, alpha := SA}, #{blue := DV, alpha := DA}) -> SV*SA + DV*DA*(1.0 - SA).
Compile (file color.erl) and test:
> c(color). {ok,color} > C1 = color:new(0.3,0.4,0.5,1.0). #{alpha => 1.0,blue => 0.5,green => 0.4,red => 0.3} > C2 = color:new(1.0,0.8,0.1,0.3). #{alpha => 0.3,blue => 0.1,green => 0.8,red => 1.0} > color:blend(C1,C2). #{alpha => 1.0,blue => 0.5,green => 0.4,red => 0.3} > color:blend(C2,C1). #{alpha => 1.0,blue => 0.38,green => 0.52,red => 0.51}
This example warrant some explanation:
-define(is_channel(V), (is_float(V) andalso V >= 0.0 andalso V =< 1.0)).
First we define a macro is_channel to help with our guard tests. This is only here for convenience and to reduce syntax cluttering. You can read more about Macros in the Erlang Reference Manual.
new(R,G,B,A) when ?is_channel(R), ?is_channel(G), ?is_channel(B), ?is_channel(A) -> #{red => R, green => G, blue => B, alpha => A}.
The function new/4 creates a new map term with and lets the keys red, green, blue and alpha be associated with an initial value. In this case we only allow for float values between and including 0.0 and 1.0 as ensured by the ?is_channel/1 macro for each argument. Only the => operator is allowed when creating a new map.
By calling blend/2 on any color term created by new/4 we can calculate the resulting color as determined by the two maps terms.
The first thing blend/2 does is to calculate the resulting alpha channel.
alpha(#{alpha := SA}, #{alpha := DA}) -> SA + DA*(1.0 - SA).
We fetch the value associated with key alpha for both arguments using the := operator. Any other keys in the map are ignored, only the key alpha is required and checked for.
This is also the case for functions red/2, blue/2 and green/2.
red(#{red := SV, alpha := SA}, #{red := DV, alpha := DA}) -> SV*SA + DV*DA*(1.0 - SA).
The difference here is that we check for two keys in each map argument. The other keys are ignored.
Finally we return the resulting color in blend/3.
blend(Src,Dst,Alpha) when Alpha > 0.0 -> Dst#{ red := red(Src,Dst) / Alpha, green := green(Src,Dst) / Alpha, blue := blue(Src,Dst) / Alpha, alpha := Alpha };
We update the Dst map with new channel values. The syntax for updating an existing key with a new value is done with := operator.
2.7 Standard Modules and Manual Pages
Erlang has a lot of standard modules to help you do things. For example, the module io contains a lot of functions to help you do formatted input/output. To look up information about standard modules, the command erl -man can be used at the operating shell or command prompt (i.e. at the same place as that where you started erl). Try the operating system shell command:
% erl -man io
ERLANG MODULE DEFINITION io(3)
MODULE
io - Standard I/O Server Interface Functions
DESCRIPTION
This module provides an interface to standard Erlang IO
servers. The output functions all return ok if they are suc-
...
If this doesn't work on your system, the documentation is included as HTML in the Erlang/OTP release, or you can read the documentation as HTML or download it as PDF from either of the sites www.erlang.se (commercial Erlang) or www.erlang.org (open source), for example for release R9B:
http://www.erlang.org/doc/r9b/doc/index.html
2.8 Writing Output to a Terminal
It's nice to be able to do formatted output in these example, so the next example shows a simple way to use to use the io:format function. Of course, just like all other exported functions, you can test the io:format function in the shell:
31> io:format("hello world~n", []). hello world ok 32> io:format("this outputs one Erlang term: ~w~n", [hello]). this outputs one Erlang term: hello ok 33> io:format("this outputs two Erlang terms: ~w~w~n", [hello, world]). this outputs two Erlang terms: helloworld ok 34> io:format("this outputs two Erlang terms: ~w ~w~n", [hello, world]). this outputs two Erlang terms: hello world ok
The function format/2 (i.e. format with two arguments) takes two lists. The first one is nearly always a list written between " ". This list is printed out as it stands, except that each ~w is replaced by a term taken in order from the second list. Each ~n is replaced by a new line. The io:format/2 function itself returns the atom ok if everything goes as planned. Like other functions in Erlang, it crashes if an error occurs. This is not a fault in Erlang, it is a deliberate policy. Erlang has sophisticated mechanisms to handle errors which we will show later. As an exercise, try to make io:format crash, it shouldn't be difficult. But notice that although io:format crashes, the Erlang shell itself does not crash.
2.9 A Larger Example
Now for a larger example to consolidate what we have learnt so far. 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 (as in the previous list). 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
Before we look at how this program works, notice that we have added a few comments to the code. 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, the other functions are local functions, i.e. they are not visible from outside the module tut5.
Note as well that when testing the program from the shell, I had to spread the input over two lines as the line was too long.
When we call format_temps the first time, City gets the value {moscow,{c,-10}} and Rest is the rest of the list. So we call the function print_temp(convert_to_celsius({moscow,{c,-10}})).
Here we see a function call as convert_to_celsius({moscow,{c,-10}}) as the argument to the function print_temp. When we nest function calls like this we execute (evaluate) them from the inside out. I.e. we first evaluate convert_to_celsius({moscow,{c,-10}}) which gives the value {moscow,{c,-10}} as the temperature is already in Celsius and then we evaluate print_temp({moscow,{c,-10}}). The function convert_to_celsius works in a similar way to the convert_length function in the previous example.
print_temp simply calls io:format in a similar way to what has been described above. 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. (Yes, this is recursion, but don't let that worry you). 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.
2.10 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
First note that we have two functions here with the same name list_max. However each of these takes a different number of arguments (parameters). In Erlang these are regarded as completely different functions. Where we need 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.
This is an example where we walk through a list "carrying" a value with us, in this case Result_so_far. list_max/1 simply 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 and 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. Note that the Erlang philosophy is not to handle errors of this type in the function they occur, but to do so elsewhere. More about this later.
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 we use before the -> in the function to say that we should only use this part of the function if the test which follows is true. We call tests of this type a guard. If the guard isn't true (we say the guard fails), we 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. (see the chapter "Guard Sequences" in the Erlang Reference Manual).
To change the above program to one which works out the minimum value of the element in a list, all we would need to do is to write < instead of >. (But it would be wise to change the name of the function to list_min :-).
Remember that I mentioned earlier that a variable could only be given a value once in its scope? In the above we see, for example, that Result_so_far has been given several values. This is OK since every time we call list_max/2 we create a new scope and one can regard the Result_so_far as a completely different variable in each scope.
Another way of creating and giving a variable a value is by using the match operator = . So if I write M = 5, a variable called M will be created and given the value 5. If, in the same scope I then write M = 6, I'll get an error. Try this out in the shell:
39> M = 5. 5 40> M = 6. ** exception error: no match of right hand side value 6 41> M = M + 1. ** exception error: no match of right hand side value 6 42> N = M + 1. 6
The use of the match operator is particularly useful for pulling apart Erlang terms and creating new ones.
43> {X, Y} = {paris, {f, 28}}. {paris,{f,28}} 44> X. paris 45> Y. {f,28}
Here we see that X gets the value paris and Y{f,28}.
Of course if we try to do the same again with another city, we get an error:
46> {X, Y} = {london, {f, 36}}.
** exception error: no match of right hand side value {london,{f,36}}
Variables can also be used to improve the readability of programs, for example, in the list_max/2 function above, we could write:
list_max([Head|Rest], Result_so_far) when Head > Result_so_far -> New_result_far = Head, list_max(Rest, New_result_far);
which is possibly a little clearer.
2.11 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]
Now an example of this when working with lists - 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 [], we then successively take off the heads of the list to be reversed and add them to the 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]
The module lists contains a lot of functions for manipulating lists, for example for 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)).
Now lets get back to the cities and temperatures, but take a more structured approach this time. First let's convert the whole 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 we see that 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)];
We go on doing this 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
We now have to add a function to find the cities with the maximum and minimum temperatures. The program below isn't the most efficient way of doing this as we walk through the list of cities four times. But it is better to first strive for clarity and correctness and to make programs efficient only if really needed.
-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
2.12 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)
Notice that tut9:test_if(33,33) did not cause any condition to succeed so we got the run time error if_clause, here nicely formatted by the shell. See the chapter "Guard Sequences" in the Erlang Reference Manual for details of the many guard tests available. case is another construct in Erlang. Recall that we wrote the convert_length function as:
convert_length({centimeter, X}) -> {inch, X / 2.54}; convert_length({inch, Y}) -> {centimeter, Y * 2.54}.
We could also write the same program 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}
Notice that 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 behaviour of case can also be modified by using guards. An example should hopefully clarify this. The following example tells us the length of a month, given the year. We need to know the year of course, 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
2.13 Built In Functions (BIFs)
Built in functions BIFs are functions which for some reason is built in to the Erlang virtual machine. BIFs often implement functionality that is impossible to implement in Erlang or is too inefficient to implement in Erlang. Some BIFs can be called by use of the function name only but they are by default belonging to the erlang module so for example the call to the BIF trunc below is equivalent with a call to erlang:trunc.
As you can see, we first find out if a year is leap or not. If a year is divisible by 400, it is a leap year. To find this out we first divide the year by 400 and use the built in function trunc (more later) to cut off any decimals. We then multiply by 400 again and see if we get back the same value. For example, year 2004:
2004 / 400 = 5.01 trunc(5.01) = 5 5 * 400 = 2000
and we can see that we got back 2000 which is not the same as 2004, so 2004 isn't divisible by 400. Year 2000:
2000 / 400 = 5.0 trunc(5.0) = 5 5 * 400 = 2000
so we have a leap year. The next two tests if the year is divisible by 100 or 4 are done in the same way. The first if returns leap or not_leap which lands up in the variable Leap. We use this variable in the guard for feb in the following case which tells us how long the month is.
This example showed the use of trunc, an easier way would be to use the Erlang operator rem which gives the remainder after division. For example:
74> 2004 rem 400.
4
so instead of writing
trunc(Year / 400) * 400 == Year -> leap;
we could write
Year rem 400 == 0 -> leap;
There are many other built in functions (BIF) such as trunc. Only a few built in functions can be used in guards, and you cannot use functions you have defined yourself in guards. (see the chapter "Guard Sequences" in the Erlang Reference Manual) (Aside for advanced readers: This is to ensure that guards don't have side effects). Let's play with a few of these functions in the shell:
75> trunc(5.6). 5 76> round(5.6). 6 77> length([a,b,c,d]). 4 78> float(5). 5.0 79> is_atom(hello). true 80> is_atom("hello"). false 81> is_tuple({paris, {c, 30}}). true 82> is_tuple([paris, {c, 30}]). false
All the above can be used in guards. Now for some which can't be used in guards:
83> atom_to_list(hello). "hello" 84> list_to_atom("goodbye"). goodbye 85> integer_to_list(22). "22"
The 3 BIFs above do conversions which would be difficult (or impossible) to do in Erlang.
2.14 Higher Order Functions (Funs)
Erlang, like most modern functional programming languages, has higher order functions. We start with an example using the shell:
86> Xf = fun(X) -> X * 2 end. #Fun<erl_eval.5.123085357> 87> Xf(5). 10
What we have done here is to define a function which doubles the value of number and assign this function to a variable. Thus 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, []) -> [].
These two functions are provided in the standard module lists. foreach takes a list and applies a fun to every element in the list, map creates a new list by applying a fun to every element in a list. Going back to the shell, we start by using map and a fun 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 lets 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
We will 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 we use it as a fun:
lists:map(fun convert_to_c/1, List)
When we use a function defined elsewhere as a fun we can refer to it as Function/Arity (remember that Arity = number of arguments). So in the map call we write lists:map(fun convert_to_c/1, List). As you can see convert_list_to_c becomes much shorter and easier to understand.
The standard module lists also contains a function sort(Fun, List) where Fun is a fun with two arguments. This fun should return true if the the first argument is less than the second argument, or else false. We add sorting 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 we use the fun:
fun({_, {c, Temp1}}, {_, {c, Temp2}}) -> Temp1 < Temp2 end,
Here we introduce the concept of an anonymous variable "_". This is simply shorthand for a variable which is going to get a value, but we will ignore the value. This can be used anywhere suitable, not just in fun's. Temp1 < Temp2 returns true if Temp1 is less than Temp2.