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

http://www.claystuart.com/

 

What is Erlang?

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.

Functional programming versus other paradigms

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.

Where to get Erlang

http://www.erlang.org/download.html

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.

         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.

Entering a simple sum

 

          

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().

%


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.

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

 

 

Data Types

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.

Adding 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.

Floats 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.


Atoms

An atom is a literal, a constant with name.

Examples:

Atoms

 

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.

Comparing atoms

 

11> abc == def.

false

 

Atoms are themselves ordered lexically (that is, z has a greater value than a), for example.

Atoms ordered lexically

 

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

         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.

Tuples

 

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

 

 


Lists

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

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]

 


List manipulation

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

Constructing 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.

The same is true for strings

 

37> "hello" ++ "world".

"helloworld"

40> ("hello" -- "ello") ++ "i".

"hi"

 

 


Expressions and pattern matching

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.

Binding a value to a variable

 

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

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.

The basic Fibonacci function

   

 

           

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

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.

the fib.erl file

 

-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.