SE 765 - Distributed Software Development CS 610 - Introduction to Parallel
and Distributed Computing |
Erlang 1 – Basic Programming |
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
Erlang was developed by Ericsson to aid
in the development of software for managing a number of different telecom
projects, with the first version being released in 1986, and the first open
source release of the language in 1998. You can see this in the extended Erlang release information where the Open Telecom Platform
(OTP), the application development platform for Erlang,
exists as the primary method of delivering the Erlang
development environment.
Erlang provides a number of standard
features not found in or difficult to manage in other languages. Much of this
functionality exists in Erlang because of it's telecom roots.
For
example, Erlang includes a very simple concurrency
model, allowing individual blocks of code to be executed multiple times on the
same host with relative ease. In addition to this concurrency Erlang uses an error model that allows failures within
these processes to be identified and handled, even by a new process, which
makes building highly fault tolerant applications very easy. Finally, Erlang includes built-in distributed processing, allowing
components to be run on one machine while being requested from another.
Put
together, Erlang provides a great environment to
build the type of distributed, scalable, and high-performance discrete
applications that we often use to support modern network and web-based
applications.
A main
difference between Erlang and more popular languages
is that Erlang is primarily a functional programming
language. This has nothing to do with whether it supports functions, but is
related to how the operation of programs and components works.
With
functional programming, the functions and operations of the language are
designed in a similar way to mathematical calculations, in that the language
operates on functions taking input and generating a result. The functional
programming paradigm means that the individual blocks of code can produce
consistent output values for the same input values. This makes predicting the
output of the function or program much easier and, therefore easier to debug
and analyze.
The
contrasting programming paradigm is the imperative programming language, such
as Perl, or Java, which rely on changing the state of the application during
execution. The change of state in imperative programming languages means that
the individual components of a program can produce different results with the
same input values, based on the state of the program at the time.
The
functional programming approach is easy to understand, but can be difficult to
apply if you are used to the more procedural and state-focused imperative
languages.
http://www.erlang.org/download.html
•
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.
•
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.
Using the Erlang shell
$ erl Erlang R13B04 (erts-5.7.5)
[source] [rq:1] [async-threads:0] Eshell V5.7.5 (abort with ^G) 1> |
•
In Windows, the shell is started by
double-clicking on the Erlang shell icon.
•
You can use the
prompt to enter statements, which you should terminate with a period.
•
The shell evaluates
the statement when completed.
•
Thus, entering a
simple sum returns the result.
1> 3+4. 7 |
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(). % |
•
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.
Here's the code to enter for a program that
doubles the value of numbers:
-module(tut). -export([double/1]). double(X) -> 2
* X. |
•
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 - tut.
o 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".
o Here
the file name is tut.erl.
•
The
second line: -export([double/1]) says
that the module tut contains
a function called double
which takes one argument (X in example) and
that this function can be called from outside the module tut.
If
you want to change directory at run time in the emulator, then use the built in
function cd as below to change to your own directory path
50> cd("c:/MyPath/erl"). c:/MyPath/erl ok |
Compile
the program in the Erlang shell:
3> c(tut). {ok,tut} |
The
{ok,tut} tells you that
the compilation was OK.
Run
the program.
4> tut:double(10). 20 |
As
expected double of 10 is 20.
•
When the function is used in
another module, we use the syntax, module_name:function_name(arguments).
•
So
tut:double(10) means call
function double
in module tut with argument
"10".
A
function can have many arguments.
-module(tut1). -export([fac/1, mult/2]). fac(1) -> 1; fac(N) -> N * fac(N -
1). mult(X, Y) -> X * Y. |
Note
that the -export
line has been expanded as well with the
information that there is another function mult with two
arguments.
Running
it gives:
8> tut1:mult(3,4). 12 |
There are two types of numeric literals, integers
and floats. Besides the conventional notation, there are two Erlangs pecific notations:
•
$char
o ASCII
value of the character char.
•
base#value
o Integer
with the base base, which must be an integer in the
range 2..36.
o In
Erlang 5.2/OTP R9B and earlier versions, the allowed
range is 2..16.
Examples:
1> 42. 42 2> $A. 65 3> $\n. 10 4> 2#101. 5 5> 16#1f. 31 6> 2.3. 2.3 7> 2.3e3. 2.3e3 8> 2.3e-3. 0.0023 |
•
Integers, and most
integer operations, are the same as in other languages. You can add two numbers
together.
1> 3+4. 7 |
And you can use
parentheses to group calculations together.
Using parentheses to group calculations
2> (4+5)*9 2>
. 81 |
Note that in the
above, the
terminating statement period is on a separate line and evaluates the preceding
calculation.
Floats in Erlang are used to represent real numbers, and can be
expressed naturally.
3> 4.5 + 6.2 . 10.7 |
Floats can also be
expressed using exponents.
Floats expressed using exponents
4> 10.9E-2 +4.5 4>
. 4.609 |
The standard
mathematical operators, +, -, /, * are supported on both integer and floating point values,
and you can mix and match floating-point and integers in calculations.
However, an error
will be raised when using an equivalent of the modulus and remainder operators
on floating values, as these support only integer values.
An atom is a literal, a constant
with name.
Examples:
8> abc. abc 9> 'Quoted
literal'. 'Quoted literal' |
·
Atoms should be used as a clearer method of specifying or
identifying a value.
·
As such, the only valid operation on an atom is a
comparison.
·
Using atoms in this way also extends to boolean logic, with the
availability of true and false atoms to identify the boolean
result of a statement.
·
Atoms must start with a lower case character, or you can
delimit with single quotes.
For example, you can
compare integers and get a boolean
atom as the result.
Comparing integers to get a boolean atom
10> 1 == 1. true |
Or you can compare
atoms, as shown below.
11> abc == def. false |
Atoms are themselves
ordered lexically (that is, z has a greater value
than a), for example.
13> a < z. true |
Standard boolean operators are available,
such as and, or, xor and not. You can also use
the is_boolean() function to check whether the supplied
value is true or false.
Example: tut2.erl) 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. |
Compilation and running:
9> c(tut2). {ok,tut2} 10> tut2:convert(3,
inch). 1.1811023622047243 11> tut2:convert(7,
centimeter). 17.78 |
•
Tuples are a
composite data type and are used to store collections of items.
•
Tuples are delimited
by curly brackets.
•
Each term Term
in
the tuple is called an element.
•
The number of elements is said to be the
size of the tuple.
14> {abc, def, {0, 1}, ghi}. {abc,def,{0,1},ghi} |
·
The contents of a
tuple do not all have to be the same type, but one special construct is of a
tuple where the first value is an atom.
·
In this case the
first atom is called a tag and this can be used to identify or classify the
contents.
Tuple where the first value is an atom
16> { email, 'example@example.org'}. {email,'example@example.org'} |
Here the tag is
email, and the tag can be used to help identify the remainder of the content in
the tuple.
Tuples are very
useful for containing defined elements and describing different complex data
structures, and Erlang
allows you to set and get values in a tuple explicitly).
Setting and getting values in a tuple explicitly
17> element(3,{abc,def,ghi,jkl}). ghi 18> setelement(3,{abc,def,ghi,jkl},mno). {abc,def,mno,jkl} |
Note that the
elements of the tuple are indexed with 1 as the first value, instead of 0, which is common in most other languages.
You can also compare
tuples in their entirety.
Comparing tuples in their entirety
19> {abc,def} == {abc,def}. true 20> {abc,def} == {abc,mno}. false |
The last data type
is the list, which is denoted by square brackets.
Lists and tuples are
similar, but whereas a tuple can only be
used in a comparison, lists allow a
wider variety of manipulation operations to be performed.
A list is a compound data type
with a variable number of terms.
[Term1,...,TermN] |
•
Each
term Term in the list is
called an element.
•
The
number of elements is said to be the length of the list.
•
Formally,
a list is either the empty list [] or consists of a
head (first element) and a tail (remainder of the list) which is also
a list.
•
The
latter can be expressed as [H|T]. The notation [Term1,...,TermN] above is
actually shorthand for the list [Term1|[...|[TermN|[]]]].
Example:
[] is
a list, thus
[c|[]]
is a list, thus
[b|[c|[]]]
is a list, thus[a|[b|[c|[]]]]
is a list, or in short [a,b,c].
1> L1 = [a,2,{c,4}]. [a,2,{c,4}] 2> [H|T] = L1. [a,2,{c,4}] 3> H. a 4> T. [2,{c,4}] 5> L2 = [d|T]. [d,2,{c,4}] 6> length(L1). 3 7> length([]). 0 |
Strings are a
special type of list.
·
Erlang does not support the idea of a string directly, although
you can use a double quoted value to create a string value.
Using a double quoted value to create a string value
23>
"Hello". "Hello" |
·
However, a string is
actually just a list of ASCII integer values.
·
Thus the above
string is stored as a list of the ASCII character values.
String stored as a list of the ASCII character values
24>
[72,101,108,108,111]. "Hello" |
As a shortcut, you
can also specify characters using the $Character notation
Specifying characters using the $Character notation
25> [$H,$e,$l,$l,$o]. "Hello" |
Lists,
including strings (lists of characters), support a number of different methods
for manipulation.
This highlights the
main difference between a string and an atom:
·
Atoms
are static identifiers, but you can manipulate a string by examining the
component parts (each character).
·
You
cannot, for example, identify the individual words in the atom (for example, 'Quick brown fox')
because the atom is a single entity.
·
But
a string could be split into different words: ["Quick","brown","fox"].
·
A
number of functions for manipulating lists are provided in the lists module
·
For
example, you can sort the items in a list using the sort function. As these are
note built-in functions, you have to specify the module and function name.
Specifying the module and function name
34> lists:sort([4,5,3,2,6,1]). [1,2,3,4,5,6] |
·
You
can construct lists with multiple elements using the constructor, which builds
a list from an element and another list.
·
In
other languages, this kind of construction is handled by functions or operators
for push().
·
In
Erlang, the | (pipe) operator is used to differentiate between the
head (start of the list) and the tail, in the notation [Head|Tail].
·
Here
the head is a single element, and the tail is the rest of the list
·
The
code below shows how to add a new value to the beginning of the list.
Adding a new value to beginning of the list
29> [1|[2,3]]. [1,2,3] |
You can repeat this
for the entire list.
Repeating this for the entire list
31> [1|[2|[3|[]]]]. [1,2,3] |
In this example, the
empty list at the end means that you have constructed a well-formed (proper)
list.
Note that the first
item must an element, not a list fragment. If you merge the other way, you
construct a nested list.
30> [[1,2]|[2,3]]. [[1,2],2,3] |
Finally, you can
merge lists using the ++ operator.
Using the ++ operator to merge lists
35> [1,2] ++ [3,4]. [1,2,3,4] |
And, you can
subtract each element from the list on the right of the operator from the list
on the left.
Subtracting each element from the list
36> [1,2,3,4] -- [2,4]. [1,3] |
Because strings are
lists, the same is true for them too.
37>
"hello" ++ "world". "helloworld" 40>
("hello" -- "ello") ++ "i". "hi" |
One important
element of expressions is the variable.
Variables in Erlang must start with a capital letter and be followed by
any combination of upper and lowercase letters and underscores.
Variables in Erlang
41> Value = 99. 99 |
·
Assignment
of a variable within Erlang is a one-time binding of
the value to the variable.
·
Once
you have bound the variable once, you cannot change it's value.
42> Value = 100. ** exception error:
no match of right hand side value 100 |
·
This
is unlike most languages — the very definition of variable tends to imply that
the value is variable.
·
Single
assignment in this means that if you want to calculate the result of a value
you must assign it to a new variable.
Limitations of variables in Erlang
43> Sum = Value +
100 199 |
·
The
benefit of this single assignment is that it makes it more difficult to
introduce or accidentally change the value of a variable during a calculation, which
makes identifying a value and debugging during programming much easier.
·
It
also makes the code clearer, and sometimes much shorter, as you can simplify
the structure.
·
Note that this
operation means that the value is calculated before being bound to the
variable.
·
You can explicitly
forget the bounding of a variable using f(Varname), or all variables using f().
Assigning values to
variables is actually a special type of pattern matching.
·
Pattern
matching in Erlang also handles the execution flow of
individual statements, and extracts the individual vales from compound data
types (tuples, arrays).
·
The
basics of pattern matching is therefore: Pattern =
Expression.
o
The expression is
composed of data structures, bound variables (that is, those with a value),
mathematical operations and function calls.
o
The two sides of the
operation must match (that is, if you have 2 element tuple as the pattern, the
expression must resolve to a 2 element tuple).
o
When the expression
is executed, the expression is evaluated, and the results assigned to the
pattern.
o
For example, we can
assign values to two variables simultaneously with one pattern matching.
Assigning values to two variables simultaneously
48> {A,B} = {(9+45),abc}. {54,abc} |
Note
:
·
if the pattern is a bound variable, or the element of the
pattern is a bound variable, then the result of the pattern match becomes a
comparison.
·
This allows for more
powerful selective assignment.
o
For example, to get
the name and email from a tuple, we can use pattern matching: {contact, Name, Email} = {contact,
"Me", "me@example.com"}.
·
Finally, we can use
pattern matching to pull out the elements from a list or tuple using the
construct notation mentioned earlier.
o
For example, the following code shows how
to get the first two elements from a list while retaining the remainder.
Getting the first two elements from a list while
retaining the remainder
53> [A,B|C] = [1,2,3,4,5,6]. [1,2,3,4,5,6] |
·
A has been assigned the value 1,
·
B the value 2,
·
C the remainder of the list.
Functions in Erlang are the basic building blocks of any program, just
as with any other language.
·
A function is
composed of the function name (defined by an atom), and zero or more arguments
to the function in parentheses: sum(N,M) -> N+M.
·
Functions must be
defined within modules in files (you cannot define them from within the shell).
·
The arguments can
contain more complex types. For example, tags in tuples can be used to select
different operations.
Tags in tuples can be used to select different operations
mathexp({sum, N,M}) ->
N+M ; mathexp({sub, N,M}) ->
N-M ; mathexp({square, N}) ->
N*N. |
The semicolon is an or operator between each function definition.
Pattern matching is
used to evaluate the arguments to the function, so if you supply a tuple with
three elements to the mathexp() function, the pattern matching will fail.
Functions in Erlang can also accept a different number of arguments,
with the right definition of the function being selected by evaluating the
pattern match until a valid definition of the function has
been located.
The number of
arguments to a function is called it's arity, and is used to help identify the
functions.
fibo(0) -> 0 ; fibo(1) -> 1 ; fibo(N) when N > 0
-> fibo(N-1) + fibo(N-2)
. |
·
The first line
defines the result of calling fibo(0) (the -> separates the definition and the body of the function)
·
The second line
defines the result when calling fibo(1)
·
Third line defines
the calculation to execute when we have been supplied a positive value of N.
This works because
of a system in Erlang called pattern matching.
·
When fibo(0) was called, this patten matched the first definition of the function,
returning the value
·
And fibo(1) matched the second
definition
·
While any other
value matched the last definition.
·
It also demonstrates
how the recursion of the function execution works.
o
When calling fibo(9), for example, the fibo(N) function is called with the corresponding value until
the fibo(0) and fibo(1) functions are reached, and when the fixed values are
returned.
The return value of
any function is the result of the last expression in that clause (in our
examples there is only one line).
Note that variables are
assigned only when a match is found, and when the variables are local to the
function.
Modules, just like
modules in other languages, are used to collate similar functions together.
·
You specify the
module name in the file (and the file name must match)
·
Then specify the
functions within the module that you want to export to other programs that load
the module.
·
For example, the code
shows the file fib.erl, which contains the definition
of our fib module.
-module(fib). -export([fibo/1, printfibo/1]). %% print fibo arg. and result, with function as parameter printfibo(N) -> Res = fib:fibo(N), io:fwrite("~w
~w~n", [N, Res]). fibo(0) -> 0 ; fibo(1) -> 1 ; fibo(N) when N > 0
-> fibo(N-1) + fibo(N-2)
. |
The module
specification is in the -module() line.
·
The -export() contains the list
of functions to be exported.
·
The definition for
each function shows the function name with the arity
of the function. This enables you to export very specific definitions of the
function.
·
To use the module it
needs to be compiled and loaded. You can do this within the shell using the c() statement, as shown
below.
Using the c() statement to
compile and load a module
1> c(fib). {ok,fib} 2> fib:printfibo(9). 9 34 ok |
Note that the
function call includes the module name to ensure that we are calling the printfibo() function within the fib module.