Chapter 2. Basic Erlang

This chapter is where we start covering the basics of Erlang. You may expect we’ll just be covering things you have seen before in programming languages, but there will be some surprises, whether your background is in C/CC++, Java, Python, or functional programming. Erlang has assignment, but not as you know it from other imperative languages, because you can assign to each variable only once. Erlang has pattern matching, which not only determines control flow, but also binds variables and pulls apart complex data structures. Erlang pattern matching is different in subtle ways from other functional languages. So, you’ll need to read carefully! We conclude the chapter by showing how to define Erlang functions and place them into modules to create programs, but we start by surveying the basic data types in Erlang.

Integers

Integers in Erlang are used to denote whole numbers. They can be positive or negative and expressed in bases other than 10. The notion of a maximum size of integers in Erlang does not exist, and so arbitrarily large whole numbers can be used in Erlang programming. When large integers do not fit in a word, they are internally converted to representation using an arbitrary number of words, more commonly known as bignums. While bignums give completely accurate calculation on arbitrary-size integers, this makes their implementation less efficient than fixed-size integers. The only limit on how large an integer can become depends on the physical constraints of the machine, namely the available memory. Some examples of integers include:

−234 0 10 100000000

To express integers in a base other than 10, the Base#Value notation is used. The base is an integer between 2 and 16, and the value is the number in that base; for example, 2#1010 denotes 10 in base 2, and −16#EA denotes –234 in base 16, since the letters A through F are used to denote the numbers 10 through 15 in base 16:

2#1010 −16#EA

To express characters as ASCII values, the $Character notation is used. $Character returns the ASCII value of Character. $a represents the integer 97 and $A represents the integer 65. The ASCII value representation of a newline, $ , is 10:

$a $A $

The Erlang Shell

Start an Erlang shell by typing erl at the command prompt in a Unix shell, or in Windows by clicking the Erlang icon in the Start menu. More details about obtaining and running Erlang are given in the Appendix. When you get the Erlang command prompt (of the form number>), try typing some integers in their various notations. Do not forget to terminate your expression with a period or full stop (.), and then press the Enter key:

1> −234.
-234
2> 2#1010.
10
3> $A.
65

If you do not type a full stop at the end of your input, the Erlang shell will not evaluate what you have typed and will continue to collect input until you type a terminating full stop and press Enter:

4> 5-
4>
4> 4.
1

The 1>, 2>, and so on are the command prompts, which show that the Erlang shell is ready to receive an input expression. When you press Enter, and the line you typed in is terminated by a full stop, the shell will evaluate what you typed, and, if successful, will display the result. Note how the various integer notations are all translated and displayed in base 10 notation. If you type an invalid expression, you will get an error, as in:

4> 5-.
* 1: syntax error before: '.'
5> q().

Ignore errors for the time being, as we will cover them in Chapter 3. To recover from having made an error, just press the Enter key a few times, add a full stop, and terminate with a final press of the Enter key. If you want to exit the shell, just type q() followed by a full stop.

Floats

Floats in Erlang are used to represent real numbers. Some examples of floats include:

17.368 −56.654 1.234E-10.

The E-10 is a conventional floating-point notation stating that the decimal point has been moved 10 positions to the left: 1.234E-10 is the same as writing 1.234×10−10, namely 0.0000000001234. The precision of the floats in Erlang is given by the 64-bit representation in the IEEE 754–1985 standard. Before going on to the next section, try typing a few floats in the Erlang shell.

Note

Soft real-time aspects of telecom applications rarely rely on floats. So historically, the implementation of efficient floating-point operations was a low priority for the Erlang virtual machine (VM). When Björn Gustavsson, one of the VM’s maintainers, started working on a hobby project focused on modeling 3D graphics, Wings3D, he was not satisfied with the performance. Operations on real numbers suddenly became much more efficient. This was, of course, a great boon for anyone doing real number computations (e.g., graphics) in Erlang.

Mathematical Operators

Operations on integers and floats include addition, subtraction, multiplication, and division. As previously shown, + and – can be used as unary operators in front of expressions of the format Op Expression, such as –12 or +12.5. Operations on integers alone always result in an integer, except in the case of floating-point division, where the result is a float. Using div will result in an integer without a remainder, which has to be computed separately using the rem operator. Table 2-1 lists the arithmetic operators.

Table 2-1. Arithmetic operators

Type

Description

Data type

+

Unary +

Integer | Float

Unary

Integer | Float

*

Multiplication

Integer | Float

/

Floating-point division

Integer | Float

div

Integer division

Integer

rem

Integer remainder

Integer

+

Addition

Integer | Float

Subtraction

Integer | Float

All mathematical operators are left-associative. In Table 2-1, they are listed in order of precedence. The unary + and − have the highest precedence; multiplication, division, and remainder have the next highest precedence; and addition and subtraction have the lowest precedence.

So, for example, evaluating −2 + 3 / 3 will first divide 3 by 3, giving the float 1.0, and then will add −2 to it, resulting in the float −1.0. You can see here that it is possible to add an integer to a float: this is done by first coercing the integer to a float before performing the addition.

To override precedence, use parentheses: (−2 + 3) * 4 will evaluate to 4, whereas −2 + 3 * 4 gives the result 10 and −(2 + 3 * 4) evaluates to −14.

Now, let’s use the Erlang shell as a glorified calculator[4] and test these operators. Note the results, especially when mixing floats and integers or dealing with floating-point division. Try out various combinations yourself:

1> +1.
1
2> −1.
-1
3> 11 div 5.
2
4> 11 rem 5.
1
5> (12 + 3) div 5.
3
6> (12+3)/5.
3.00000
7> 2*2*3.14.
12.5600
8> 1 + 2 + 3 + 5 + 8.
19
9> 2*2 + −3*3.
-5
10> 1/2 + (2/3 + (3/4 + (4/5))) - 1.
1.71667

Before going on to the next section, try typing 2.0 rem 3 in the shell:

13> 2.0 rem 3.
** exception error: bad argument in an arithmetic expression
     in operator  rem/2
        called as 2.0 rem 3

You are trying to execute an operation on a float and an integer when the Erlang runtime system is expecting two integers. The error you see is typical of errors returned by the runtime system. We will cover this and other errors in Chapter 3. If you come from a C or a Java background, you might have noticed that there is no need to convert integers to floats before performing floating-point division.

Atoms

Atoms are constant literals that stand for themselves. Atoms serve much the same purpose as values in enumeration types in other languages; for the beginner, it sometimes helps to think of them as a huge enumeration type. To compare with other languages, the role of atoms is played by #define constants in C and C++, by “static final” values in Java, and by “enums” in Ruby.

The only operations on atoms are comparisons, which are implemented in a very efficient way in Erlang. The reason you use atoms in Erlang, as opposed to integers, is that they make the code clear yet efficient. Atoms remain in the object code, and as a result, debugging becomes easier; this is not the case in C or C++ where the definitions are only introduced by the preprocessor.

Atoms start with a lowercase letter or are delimited by single quotes. Letters, digits, the “at” symbol (@), the full stop (.), and underscores (_) are valid characters if the atom starts with a lowercase letter. Any character code is allowed within an atom if the atom is encapsulated by single quotes. Examples of atoms starting with a lowercase letter include:

january fooBar alfa21 start_with_lower_case node@ramone true false

When using quotes, examples include:

'January' 'a space' 'Anything inside quotes{}#@ 
12'         
'[email protected]'

Note

The concept of atoms in Erlang was originally inspired—as were a number of aspects of the language—by the logic programming language Prolog. They are, however, also commonly found in functional programming languages.

Now try typing some atoms in the shell. If at any point, the shell stops responding, you have probably opened a single quote and forgotten to close it. Type '. and press Enter to get back to the shell command line. Experiment with spaces, funny characters, and capital letters. Pay special attention to how the quotes are (and are not) displayed and how and where the expressions are terminated with a full stop:

1> abc.
abc
2> 'abc_123_CDE'.
abc_123_CDE
3> 'using spaces'.
'using spaces'
4> 'lowercaseQuote'.
lowercaseQuote
5> '

'.
'

'
6> '1
6> 2
6> 3
6> 4'.
'1
2
3
4'
7> 'funny characters in quotes: !"£$%^&*()-='.
'funny characters in quotes: !"£$%^&*()-='
8> '1+2+3'.
'1+2+3'
9> 'missing a full stop.'
9> .
'missing a full stop.'

Booleans

There are no separate types of Boolean values or characters in Erlang. Instead of a Boolean type, the atoms true and false are used together with Boolean operators. They play the role of Booleans in being the results of tests, and in particular, comparisons:

1> 1==2.
false
2> 1<2.
true
3> a>z.
false
4> less<more.
true

Atoms are ordered in lexicographical order. We give more details of these comparisons later in this chapter. Erlang has a wide variety of built-in functions, usually called BIFs in the Erlang community, which can be used in your programs and in the shell. The built-in function is_boolean gives a test of whether an Erlang value is a Boolean:

5> is_boolean(9+6).
false
6> is_boolean(true).
true

Complex tests can be formed using the logical operators described in Table 2-2.

Table 2-2. Logical operators

Operator

Description

and

Returns true only if both arguments are true

andalso

Shortcut evaluation of and: returns false if the first argument is false, without evaluating the second

or

Returns true if either of the arguments is true

orelse

Shortcut evaluation of or: returns true if the first argument is true, without evaluating the second

xor

“Exclusive or”: returns true if one of its arguments is true and the other false

not

Unary negation operator: returns true if its argument is false, and vice versa

In the following code, the binary operators are infixed, or placed between their two arguments:

1> not((1<3) and (2==2)).
false
2> not((1<3) or (2==2)).
false
3> not((1<3) xor (2==2)).
true

Tuples

Tuples are a composite data type used to store a collection of items, which are Erlang data values but which do not have to all be the same type. Tuples are delimited by curly brackets, {...}, and their elements are separated by commas. Some examples of tuples include:

{123, bcd} {123, def, abc} {abc, {def, 123}, ghi} {}
{person, 'Joe', 'Armstrong'} {person, 'Mike', 'Williams'}

The tuple {123,bcd} has two elements: the integer 123 and the atom bcd. The tuple {abc, {def, 123}, ghi} has three elements, as the tuple {def, 123} counts as one element. The empty tuple {} has no elements. Tuples with one element, such as {123}, are also allowed, but because you could just use the element on its own “untupled,” it’s not a good idea in general to use them in your code.

In a tuple, when the first element is an atom, it is called a tag. This Erlang convention is used to represent different types of data, and will usually have a meaning in the program that uses it. For example, in the tuple {person, 'Joe', 'Armstrong'}, the atom person is the tag and might denote that the second field in the tuple is always the first name of the person, while the third is the surname.

The use of a first position tag is to differentiate between tuples used for different purposes in the code. This greatly helps in finding the cause of errors when the wrong tuple has been mistakenly passed as an argument or returned as a result of a function call. This is considered a best practice for Erlang.

A number of built-in functions are provided to set and retrieve elements as well as get the tuple size:

1> tuple_size({abc, {def, 123}, ghi}).
3
2> element(2,{abc, {def, 123}, ghi}).
{def,123}
3> setelement(2,{abc, {def, 123}, ghi},def).
{abc,def,ghi}
4> {1,2}<{1,3}.
true
5> {2,3}<{2,3}.
false
6> {1,2}=={2,3}.
false

In command 2 note that the elements of the tuple are indexed from 1 rather than zero. In the third example, the result is a new tuple, with a different value—def—in the second position, and the same values as the old tuple in the other positions. These functions are all generic in that they can be used over any kind of tuple, of any size.

Before starting to look at lists, make sure you experiment and get better acquainted with tuples and the tuple BIFs in the Erlang shell.

Lists

Lists and tuples are used to store collections of elements; in both cases, the elements can be of different types, and the collections can be of any size. Lists and tuples are very different, however, in the way that they can be processed. We begin by describing how lists are denoted in Erlang, and examine the way that strings are a special kind of list, before explaining in detail how lists can be processed.

Lists are delimited by square brackets, [...], and their elements are separated by commas. Elements in lists do not have to be of the same data type and, just like tuples, can be freely mixed. Some examples of lists include:

[january, february, march]
[123, def, abc]
[a,[b,[c,d,e],f], g]
[]
[{person, 'Joe', 'Armstrong'}, {person, 'Robert', 'Virding'},
 {person, 'Mike', 'Williams'}]
[72,101,108,108,111,32,87,111,114,108,100]
[$H,$e,$l,$l,$o,$ ,$W,$o,$r,$l,$d]
"Hello World"

The list [a,[b,[c,d,e],f], g] is said to have a length of 3. The first element is the atom a, the second is the list [b,[c,d,e],f], and the third is the atom g. The empty list is denoted by [], while [{person, 'Joe', 'Armstrong'}, {person, 'Robert', 'Virding'}, {person, 'Mike', 'Williams'}] is a list of tagged tuples.

Characters and Strings

Characters are represented by integers, and strings (of characters) are represented by lists of integers. The integer representation of a character is given by preceding the character with the $ symbol:

1> $A.
65
2> $A + 32.
97
3> $a.
97

There is no string data type in Erlang. Strings are denoted by lists of ASCII values and represented using the double quotes (") notation. So, the string "Hello World" is in fact the list [72,101,108,108,111,32,87,111,114,108,100]. And if you denote the integers using the ASCII integer notation $Character, you get [$H,$e,$l,$l,$o,$ ,$W,$o,$r,$l,$d]. The empty string "" is equivalent to the empty list []:

4> [65,66,67].
"ABC"
5> [67,$A+32,$A+51].
"Cat"
6> [72,101,108,108,111,32,87,111,114,108,100].
"Hello World"
7> [$H,$e,$l,$l,$o,$ ,$W,$o,$r,$l,$d].
"Hello World"

Atoms and Strings

What is the difference between atoms and strings? First, they can be processed in different ways: the only thing you can do with atoms is compare them, whereas you can process strings in a lot of different ways. The string "Hello World" can be split into its list of constituent words—["Hello", "World"], for instance; you can’t do the same for the atom 'Hello World'.

You could use a string to play the role of an atom, that is, as a constant literal. However, another difference between atoms and strings is efficiency. Representation of a string takes up space proportional to the string’s size, whereas atoms are represented in a system table and take a couple of bytes to reference regardless of their size. If a program is to compare two strings (or lists), it needs to compare the strings character by character while traversing both of them. When comparing atoms, however, the runtime system compares an internal identifier in a single operation.

Building and Processing Lists

As we said earlier, lists and tuples are processed in very different ways. A tuple can be processed only by extracting particular elements, whereas when working with lists, it is possible to break a list into a head and a tail, as long as the list is not empty. The head refers to the first element in the list, and the tail is another list that contains all the remaining elements; this list can itself be processed further. This is illustrated in Figure 2-1.

Just as a list can be split in this way, it is possible to build or construct a list from a list and an element. The new list is constructed like this—[Head|Tail], which is an example of a cons, short for constructor.

The recursive definition of lists
Figure 2-1. The recursive definition of lists

So, if you take the list [1,2,3], the head would be 1 and the tail would be [2,3]. Using the cons operator, the list can be represented as [1|[2,3]]. Breaking the tail further, you would get [1|[2|[3]] and [1|[2|[3|[]]]]. A final valid notation for this list is of the format [1,2|[3|[]]], where you can have more than one element separated by commas before appending the tail with the cons operator. All of these lists are equivalent to the original list [1,2,3]. If the last tail term is the empty list, you have a proper or well-formed list.

When learning Erlang, the recursive definition of lists is the first hurdle that people can stumble on. So, just to be on the safe side, here is one more example where all of the lists are semantically equivalent:

[one, two, three, four]
[one, two, three, four|[]]
[one, two|[three, four]]
[one, two|[three|[four|[]]]]
[one|[two|[three|[four|[]]]]]

Note that you must have an element on the lefthand side of the cons operator and a list on the right, both within the square brackets, for the result to be a proper or well-formed list.

In fact, lists in Erlang do not have to be proper, meaning that the tail does not necessarily have to be a list. Try typing [[1, 2]|3] in the shell. What is the result? Expressions such as [1|2] and [1,2|foo] are syntactically valid Erlang data structures, but are of only limited value.[5] Nonproper lists can be useful in supporting demand-driven or lazy programming, and we talk about that in Chapter 9. Apart from this, it is one of the conventions of Erlang programming that use of nonproper lists should be avoided. That is because it is normally impossible to determine just by inspecting the code whether their use was intentional or an error. Writing [2|3] instead of [2|[3]], for example, results in a valid Erlang expression that compiles without any errors. It will, however, generate a runtime error when the tail of the list is treated as a list and not as an atom.

List Functions and Operations

Lists are one of the most useful data types in Erlang, and, especially in combination with tuples, they can be used to represent all sorts of complex data structures. In particular, lists are often used to represent collections of objects, which can be split into other collections, combined, and analyzed.

Many operations on lists are defined in the lists library module, and you can see some examples in the following shell session. These functions are not BIFs, and so are called by putting the module name in front of the function, separated by a colon (:) as in lists:split. The effect of the functions should be clear from the examples. You’ll see how to define functions such as this in the next chapter:

1> lists:max([1,2,3]).
3
2> lists:reverse([1,2,3]).
[3,2,1]
3> lists:sort([2,1,3]).
[1,2,3]
4> lists:split(2,[3,4,10,7,9]).
{[3,4],[10,7,9]}
5> lists:sum([3,4,10,7,9]).
33
6> lists:zip([1,2,3],[5,6,7]).
[{1,5},{2,6},{3,7}]
7> lists:delete(2,[1,2,3,2,4,2]).
[1,3,2,4,2]
8> lists:last([1,2,3]).
3
9> lists:member(5,[1,24]).
false
10> lists:member(24,[1,24]).
true
11> lists:nth(2,[3,4,10,7,9]).
4
12> lists:length([1,2,3]).
** exception error: undefined function lists:length/1
13> length([1,2,3]).
3

We said these are not BIFs: the exception to this is length, as you can see in commands 12 and 13.

There are also three operators on lists. You already saw the [...|...] operator, but there are also ++ and --, which join lists and “subtract” one list from another. Here are some examples:

1> [monday, tuesday, Wednesday].
[monday,tuesday,wednesday]
2>
2> [1|[2|[3|[]]]].
[1,2,3]
3> [a, mixed, "list", {with, 4}, 'data types'].
[a,mixed,"list",{with,4},'data types']
4> [1,2,3] ++ [4,5,6].
[1,2,3,4,5,6]
5> [1,2,2,3,4,4] -- [2,4].
[1,2,3,4]
6> "A long string I have split "
6> "across several lines.".
"A long string I have split across several lines."

The ++ operator takes two lists and joins them together into a new list. So, writing [1,2] ++ [3,4] will return [1,2,3,4].

The -- operator individually subtracts each element in the list on the righthand side from the list on the lefthand side. So, [1,1] -- [1] returns [1], whereas [1,2,3,4] -- [1,4] returns [2,3]. If you evaluate [1,2] -- [1,1,3], you get the list [2]. This is because if elements on the list on the righthand side of the operation do not match, they are ignored. Both ++ and -- are right-associative, and so an expression of the form [1,2,3]--[1,3]--[1,2] will be bracketed to the right:

7> [1,2,3]--[1,3]--[1,2].
[1,2]
8> ([1,2,3]--[1,3])--[1,2].
[]

Finally, writing "Hello " "Concurrent " "World" will result in the compiler appending the three strings together, returning "Hello Concurrent World".

If you want to add an element to the beginning of a list, you can do it in two ways:

  • Using cons directly, as in [1|[2,3,4]].

  • Using ++ instead, as in [1] ++ [2,3,4].

Both have the same effect, but ++ is less efficient and can lead to programs running substantially more slowly. So, when you want to add an element to the head of the list, you should always use cons ([...|...]) because it is more efficient.

The proplists module contains functions for working with property lists. Property lists are ordinary lists containing entries in the form of either tagged tuples, whose first elements are keys used for lookup and insertion, or atoms (such as blah), which is shorthand for the tuple {blah, true}.

To make sure you have grasped what’s in this section, start the shell and test what you just learned about lists and strings:

  • Pay particular attention to the way lists are built using [...|...], which some readers may struggle with the first time around. It is important that you understand how this works, as recursion, covered in Chapter 3, builds heavily on it.

  • Look at lists that are not proper, because the next time you might come across them, they will probably be in the form of a bug.

  • The append, subtract, and string concatenation operators will make your code more elegant, so make sure you spend some time getting acquainted with them as well.

  • Remember that if the shell does not return the string you typed in, you probably forgot to close the double quotes. Type ". and press Enter a few times.

  • Also, what happens when you type in a list of ASCII values? How does the shell display them?

We’ll finish this section with a short discussion of the history of strings in the Erlang system.

Before the string concatenation construct was added to the language, programmers would make their strings span many lines. When the code became unreadable, they would often break the strings into manageable chunks and concatenate them using the append function in the lists library module.

When the ++ notation was added to the language, programmers went from using the append function to abusing the ++ operator. The ++ operator and the append function are expensive operations, as the list on the lefthand side of the expression has to be traversed. Not only are they expensive operations, but often they are redundant, as all I/O functions (including socket operations) in Erlang accept nonflat strings such as ["Hello ",["Concurrent "]|"World"].

Term Comparison

Term comparisons in Erlang take two expressions on either side of the comparison operator. The result of the expression is one of the Boolean atoms true or false. The equal (==) and not equal (/=) operators compare the values on either side of the operator without paying attention to the data types. Typing 1 == one returns false, whereas one == one returns true.

Comparisons such as 1 == 1.0 will return true and 1 /= 1.0 will return false, as integers are converted to floats before being compared with them in such a comparison. You get around this by using the operators exactly equal to and not exactly equal to, as these operators compare not only the values on either side of the equation, but also their data types. So, for example, 1 =:= 1.0 and 1 =/= 1 will both return false, and 1 =/= 1.0 returns true.

As well as comparisons for (in)equality, you can compare the ordering between values, using < (less than), =< (less than or equal to), > (greater than), and >= (greater than or equal to). Table 2-3 lists the comparison operators.

Table 2-3. Comparison operators

Operator

Description

==

Equal to

/=

Not equal to

=:=

Exactly equal to

=/=

Exactly not equal to

=<

Less than or equal to

<

Less than

>=

Greater than or equal to

>

Greater than

If the expressions being compared are of different types, the following hierarchy is taken into consideration:

number < atom < reference < fun < port < pid < tuple < list < binary

This means, for instance, that any number will be smaller than any atom and any tuple will be smaller than any list:

3> 11<ten.
true
4> {123,345}<[].
true

Lists are ordered lexicographically, like the words in a dictionary. The first elements are compared, and whichever is smaller indicates the smaller list: if they are the same, the second elements are compared, and so on. When one list is exhausted, that is the smaller list. So:

5> [boo,hoo]<[adder,zebra,bee].
false
6> [boo,hoo]<[boo,hoo,adder,zebra,bee].
true

On the other hand, when comparing tuples, the number of elements in the constructs is compared first, followed by comparisons of the individual values themselves:

7> {boo,hoo}<{adder,zebra,bee}.
true
8> {boo,hoo}<{boo,hoo,adder,zebra,bee}.
true

The ability to compare values from different data types allows you to write generic functions such as sort, where regardless of the heterogeneous contents of a list, the function will always be able to sort its elements. For the time being, do not worry about references, funs, ports, and binaries. We will cover these data types in Chapter 9 and Chapter 15.

Using the exactly equal and not exactly equal operators will provide the compiler and type tools with more information and result in more efficient code. Unfortunately, =:= and =/= are not the prettiest of operators and tend to make the code ugly. As a result, the equal and not equal operators are commonly used in programs, including many of the libraries that come with the Erlang runtime system.

Start the Erlang shell and try some of the comparison operators. Though not included in the following examples, try testing with different data types and comparing the results with the various equality operators. Make sure you also become acquainted with how the operators work with different data types:

1> 1.0 == 1.
true
2> 1.0 =:= 1.
false
3> {1,2} < [1,2].
true
4> 1 =< 1.2.
true
5> 1 =/= 1.0.
true
6> (1 < 2) < 3.
false
7> (1 > 2) == false.
true

Variables

Variables are used to store values of simple and composite data types. In Erlang, they always start with an uppercase letter,[6] followed by upper- and lowercase letters, integers, and underscores. They may not contain other “special” characters. Examples of variables include the following:

A_long_variable_name Flag Name2 DbgFlag

Erlang variables differ from variables in most conventional programming languages. In the scope of a function, including the Erlang shell process, once you’ve bound a variable, you cannot change its value. This is called single assignment. So, if you need to do a computation and manipulate the value of a variable, you need to store the results in a new variable. For example, writing the following:

Double = 2,
Double = Double * Double

would result in a runtime error, because Double is already bound to the integer 2. Trying to bind it to the integer 4 fails as it is already bound. As mentioned, the way around this feature is to bind the results in a fresh variable:

Double = 2,
NewDouble = Double * Double

Single assignment of variables might feel awkward at first, but you’ll get used to it very quickly. It encourages you to write shorter functions and puts in place a discipline that often results in code with fewer errors. It also makes debugging of errors related to incorrect values easy, as tracing the source of the error to the place where the value was bound can lead to only one place.

All calls with variables in Erlang are call by value: all arguments to a function call are evaluated before the body of the function is evaluated. The concept of call by reference does not exist, removing one way in which side effects can be caused.[7] All variables in Erlang are considered local to the function in which they are bound. Global variables do not exist, making it easier to debug Erlang programs and reduce the risk of errors and bad programming practices.

Another useful feature of Erlang variables is that there is no need to declare them: you just use them. Programmers coming from a functional programming background are used to this, whereas those coming from a C or Java background will quickly learn to appreciate it. The reason for not having to declare variables is that Erlang has a dynamic type system.[8] Types are determined at runtime, as is the viability of the operation you are trying to execute on the variable. The following code attempting to multiply an atom by an integer will compile (with compiler warnings), but will result in a runtime error when you try to execute it:

Var = one,
Double = Var * 2

At first, using variables that start with capital letters might feel counterintuitive, but you’ll get used to it quickly. After a few years of programming in Erlang, when reading C code, don’t be surprised if you react over the fact that whoever wrote the code used atoms instead of variables. It has happened to us!

Before using variables, remember: variables can be bound only once! This might be a problem in the Erlang shell, as programs are meant to run nonstop for many years and the same shell is used to interact with them. Two operations can be used as a workaround to this problem. Using f() forgets all variable bindings, whereas f(Variable) will unbind a specific Variable. You can use these operations only in the shell. Attempts to include them in your programs are futile and will result in a compiler error:

1> A = (1+2)*3.
9
2> A + A.
18
3> B = A + 1.
10
4> A = A + 1.
** exception error: no match of right hand side value 10
5> f(A).
ok
6> A.
** 1: variable 'A' is unbound **

In fact, what you see here with assignment to variables is a special case of pattern matching, which we’ll discuss shortly.

Complex Data Structures

When we refer to Erlang terms, we mean legal data structures. Erlang terms can be simple data values, but we often use the expression to describe arbitrarily complex data structures.

In Erlang, complex data structures are created by nesting composite data types together. These data structures may contain bound variables or the simple and composite values themselves. An example of a list containing tuples of type person (tagged with the atom person) with the first name, surname, and a list of attributes would look like this:

[{person,"Joe","Armstrong",
   [ {shoeSize,42},
     {pets,[{cat,zorro},{cat,daisy}]},
     {children,[{thomas,21},{claire,17}]}]
   },
 {person,"Mike","Williams",
   [ {shoeSize,41},
     {likes,[boats,wine]}]
   }
]

Or, if we were to write it in a few steps using variables, we would do it like this. Note how, for readability, we named the variables with their data types:

1> JoeAttributeList =  [{shoeSize,42},  {pets,[{cat, zorro},{cat,daisy}]},
1>                      {children,[{thomas,21},{claire,17}]}].
 [{shoeSize,42},
 {pets,[{cat,zorro},{cat,daisy}]},
 {children,[{thomas,21},{claire,17}]}]
2> JoeTuple = {person,"Joe","Armstrong",JoeAttributeList}.
{person,"Joe","Armstrong",
        [{shoeSize,42},
         {pets,[{cat,zorro},{cat,daisy}]},
         {children,[{thomas,21},{claire,17}]}]}
3> MikeAttributeList = [{shoeSize,41},{likes,[boats,wine]}].
[{shoeSize,41},{likes,[boats,wine]}]
4> MikeTuple = {person,"Mike","Williams",MikeAttributeList}.
{person,"Mike","Williams",
        [{shoeSize,41},{likes,[boats,wine]}]}
5> People = [JoeTuple,MikeTuple].
[{person,"Joe","Armstrong",
         [{shoeSize,42},
          {pets,[{cat,zorro},{cat,daisy}]},
          {children,[{thomas,21},{claire,17}]}]},
 {person,"Mike","Williams",
         [{shoeSize,41},{likes,[boats,wine]}]}]

One of the beauties of Erlang is the fact that there is no explicit need for memory allocation and deallocation. For C programmers, this means no more sleepless nights hunting for pointer errors or memory leakages. Memory to store the complex data types is allocated by the runtime system when needed, and deallocated automatically by the garbage collector when the structure is no longer referenced.

Pattern Matching

Pattern matching in Erlang is used to:

  • Assign values to variables

  • Control the execution flow of programs

  • Extract values from compound data types

The combination of these features allows you to write concise, readable yet powerful programs, particularly when pattern matching is used to handle the arguments of a function you’re defining. A pattern match is written like this:

Pattern = Expression

And as we said earlier, it’s a generalization of what you already saw when we talked about variables.

The Pattern consists of data structures that can contain both bound and unbound variables, as well as literal values (such as atoms, integers, or strings). A bound variable is a variable which already has a value, and an unbound variable is one that has not yet been bound to a value. Examples of patterns include:

Double
{Double, 34}
{Double, Double}
[true, Double, 23, {34, Treble}]

The Expression consists of data structures, bound variables, mathematical operations, and function calls. It may not contain unbound values.

What happens when a pattern match is executed? Two results are possible:

  • The pattern match can succeed, and this results in the unbound variables becoming bound (and the value of the expression being returned).

  • The pattern match can fail, and no variables become bound as a result.

What determines whether the pattern match succeeds? The Expression on the righthand side of the = operator is first evaluated and then its value is compared to the Pattern:

  • The expression and the pattern need to be of the same shape: a tuple of three elements can match only with a tuple of three elements, a list of the form [X|Xs] can match only with a nonempty list, and so on.

  • The literals in the pattern have to be equal to the values in the corresponding place in the value of the expression.

  • The unbound variables are bound to the corresponding value in the Expression if the pattern match succeeds.

  • The bound variables also must have the same value as the corresponding place in the value of the expression.

Taking a concrete example, writing Sum = 1+2 where the variable Sum is unbound would result in the sum of 1 and 2 being calculated and compared to Sum. If Sum is unbound, pattern matching succeeds and Sum is bound to 3. Just to be clear, this would not bind 1 to Sum and then add 2 to it. If Sum is already bound, pattern matching will succeed if (and only if) Sum is already bound to 3.

Let’s look at some examples in the shell:

1> List = [1,2,3,4].
[1,2,3,4]

In command 1, the pattern match succeeds, and binds the list [1,2,3,4] to the List variable:

2> [Head|Tail] = List.
[1,2,3,4]
3> Head.
1
4> Tail.
[2,3,4]

In command 2, the pattern match succeeds, because the List is nonempty, so it has a head and a tail which are bound to the variables Head and Tail. You can see this in commands 3 and 4.

5> [Head|Tail] = [1].
** exception error: no match of right hand side value [1]
6> [Head|Tail] = [1,2,3,4].
[1,2,3,4]
7> [Head1|Tail1] = [1].
[1]
8> Tail1.
[]

What goes wrong in command 5? It looks as though this should succeed, but the variables Head and Tail are bound already, so this pattern match becomes a test of whether the expression is in fact [1,2,3,4]; you can see in command 6 that this would succeed.

If you want to extract the head and tail of the list [1], you need to use variables that are not yet bound, and commands 7 and 8 show that this is now successful.

9> {Element, Element, X} = {1,1,2}.
{1,1,2}
10> {Element, Element, X} = {1,2,3}.
** exception error: no match of right hand side value {1,2,3}

What happens if a variable is repeated in a pattern, as in command 9? The first occurrence is unbound, and results in a binding: here to the value 1. The next occurrence is bound, and will succeed only if the corresponding value is 1. You can see that this is the case in command 9, but not in command 10, hence the “no match” error.

11> {Element, Element, _} = {1,1,2}.
{1,1,2}

As well as using variables, it is possible to use a wildcard symbol, _, in a pattern. This will match with anything, and produces no bindings.

12> {person, Name, Surname} = {person, "Jan-Henry", "Nystrom"}.
{person,"Jan-Henry","Nystrom"}
13> [1,2,3] = [1,2,3,4].
** exception error: no match of right hand side value [1,2,3,4]

Why do we use pattern matching? Take assignment of variables as an example. In Erlang, the expression Int = 1 is used to compare the contents of the variable Int to the integer 1. If Int is unbound, it gets bound to whatever the righthand side of the equation evaluates to, in this case 1. That is how variable assignment actually works. We are not assigning variables, but in fact pattern-matching them. If we now write Int = 1 followed by Int = 1+0, the first expression will (assuming Int is unbound) bind the variable Int to the integer 1. The second expression will add 1 to 0 and compare it to the contents of the variable Int, currently bound to 1. As the result is the same, the pattern matching will be successful. If we instead wrote Int = Int + 1, the expression on the righthand side would evaluate to 2. Attempting to compare it to the contents of Int would fail, as it is bound to 1.

Pattern matching is also used to pick the execution flow in a program. Later in this and in the following chapters, we will cover case statements, receive statements, and function clauses. In each of these constructs, pattern matching is used to determine which of the clauses has to be evaluated. In effect, we are testing a pattern match that either succeeds or fails. For example, the following pattern match fails:

{A, A, B} = {abc, def, 123}

The first comparison is to ensure that the data type on the righthand side of the expression is the same as the data type on the left, and that their size is the same. Both are tuples with three elements, so thus far, the pattern matching is successful. Tests are now done on the individual elements of the tuple. The first A is unbound and gets bound to the atom abc. The second A is now also bound to abc, so comparing it to the atom def will fail because the values differ.

Pattern matching [A,B,C,D] = [1,2,3] fails. Even if both are lists, the list on the lefthand side has four elements and the one on the right has only three. A common misconception is that D can be set to the empty list and the pattern matching succeeds. In this example, that would not be possible, as the separator between C and D is a comma and not the cons operator. [A,B, C|D] = [1,2,3] will pattern-match successfully, with the variables A, B, and C being bound to the integers 1, 2, and 3, and the variable D being bound to the tail, namely the empty list. If we write [A,B|C] = [1,2,3,4,5,6,7], A and B will be bound to 1 and 2, and C will be bound to the list containing [3,4,5,6,7]. Finally, [H|T] = [] will also fail, as [H|T] implies that the list has at least one element, when we are in fact matching against an empty list.

The last use of pattern matching is to extract values from compound data types. For example:

{A, _, [B|_], {B}} = {abc, 23, [22, 23], {22}}

will successfully extract the first element of the tuple, the atom abc, and bind it to the variable A. It will also extract the first element of the list stored in the third element of the tuple and bind it to the variable B.

In the following example

14> Var = {person, "Francesco", "Cesarini"}.
{person, "Francesco", "Cesarini"}
15 {person, Name, Surname} = Var.
{person, "Francesco", "Cesarini"}

we are binding a tuple of type person to the variable Var in the first clause and extracting the first name and the surname in the second one. This will succeed, with the variable Name being bound to the string "Francesco" and the variable Surname to "Cesarini".

We mentioned earlier that variables can start with an underscore; these denote “don’t care” variables, which are placeholders for values the program does not need. “Don’t care” variables behave just like normal variables—their values can be inspected, used, and compared. The only difference is that compiler warnings are generated if the value of the normal variable is never used. Using “don’t care” variables is considered good programming practice, informing whoever is reading the code that this value is ignored. To increase readability and maintainability, one often includes the value or type in the name of a “don’t care” variable. The underscore on its own is also a “don’t care” variable, but its contents cannot be accessed: its values are ignored and never bound.

When pattern matching, note the use of the “don’t care” variables, more specifically in the following example:

{A, _, [B|_], {B}} = {abc, 23, [22, 23], {22}}

As _ is never bound, it does not matter whether the values you are matching against are different. But writing:

{A, _int, [B|_int], {B}} = {abc, 23, [22, 23], {22}}

completely changes the semantics of the program. The variable _int will be bound to the integer 23 and is later compared to the list containing the integer 23. This will cause the pattern match to fail.

Warning

Using variables that start with an underscore makes the code more legible, but inserts potential bugs in the code when they are mistakenly reused in other clauses in the same function. Since the introduction of compiler warnings for singleton variables (variables that appear once in the function), programmers mechanically add an underscore, but tend to forget about the single assignment rule and about the fact that these variables are actually bound to values. So, use them because they increase code legibility and maintainability, but use them with care, ensuring that you do not introduce bugs.

You can see from what we have said that pattern matching is a powerful mechanism, with some subtleties in its behavior that allow you to do some amazing things in one or two lines of code, combining tests, assignment, and control.

At the risk of sounding repetitive, try pattern matching in the shell. You can experiment with defining lists to be really sure you master the concept, and use pattern matching to deconstruct the lists you have built. Make pattern-matching clauses fail and inspect the errors that are returned.[9] When you do so, experiment with both bound and unbound variables. As pattern matching holds the key to writing compact and elegant programs, understanding it before continuing will allow you to make the most of Erlang as you progress.

Functions

Now that we’ve covered data types, variables, and pattern matching, how do you use them? In programs, of course. Erlang programs consist of functions that call each other. Functions are grouped together and defined within modules. The name of the function is an atom. The head of a function clause consists of the name, followed by a pair of parentheses containing zero or more formal parameters. In Erlang, the number of parameters in a function is called its arity. The arrow (->) separates the head of the clause from its body.

Before we go any further, do not try to type functions directly in the shell. You can if you want to, but all you will get is a syntax error. Functions have to be defined in modules and compiled separately. We will cover writing, compiling, and running functions in the next section.

Example 2-1 shows an Erlang function used to calculate the area of a shape.[10] Erlang functions are defined as a collection of clauses separated by semicolons and terminated by a full stop. Each clause has a head specifying the expected argument patterns and a function body consisting of one or more comma-separated expressions. These are evaluated in turn, and the return value of a function is the result of the last expression executed.

Example 2-1. An Erlang function to calculate the area of a shape
area({square, Side}) ->
  Side * Side ;
area({circle, Radius}) ->
  math:pi() * Radius * Radius;
area({triangle, A, B, C}) ->
  S = (A + B + C)/2,
  math:sqrt(S*(S-A)*(S-B)*(S-C));
area(Other) ->
  {error, invalid_object}.

When a function is called, its clauses are checked sequentially by pattern matching the arguments passed in the call to the patterns defined in the function heads. If the pattern match is successful, variables are bound and the body of the clause is executed. If it is not, the next clause is selected and matched. When defining a function, it is a good practice to make sure that for every argument there is one clause that succeeds; this is often done by making the final clause a catch-all clause that matches all (remaining) cases.

In Example 2-1, area is a function call that will calculate the area of a square, a circle, or a triangle or return the tuple {error, invalid_object}. Let’s take an example call to the area function:

area({circle, 2})

Pattern matching will fail in the first clause, because even though we have a tuple of size 2 in the argument and parameter, the atoms square and circle will not match. The second clause is chosen and the pattern match is successful, resulting in the variable Radius being bound to the integer 2. The return value of the function will be the result of the last expression in that clause, math:pi()*2*2, namely 12.57 (rounded). When a clause is matched, the remaining ones are not executed.

The last clause with the function head area(Other) -> is a catch-all clause. As Other is unbound, it will match any call to the function area when pattern matching fails in the first three clauses. It will return an expression signifying an error: {error, invalid_object}.

A common error is shadowing clauses that will never match. The flatten function defined in the following example will always return {error, unknown_shape}, because pattern matching against Other will always succeed, and so Other will be bound to any argument passed to flatten, including cube and sphere:

flatten(Other)  -> {error, unknown_shape};
flatten(cube)   -> square;
flatten(sphere) -> circle.

Let’s look at an example of the factorial function:

factorial(0) -> 1;
factorial(N) ->
  N * factorial(N-1).

If we call factorial(3), pattern matching in the first clause will fail, as 3 does not match 0. The runtime system tries the second clause, and as N is unbound, N will successfully be bound to 3. This clause returns 3 * factorial(2). The runtime system is unable to return any value until it has executed factorial(2) and is able to multiply its value by 3. Calling factorial(2) results in the second clause matching, and in this call N is bound to 2 and returns the value 2 * factorial(1), which in turn results in the call 1 * factorial(0). The call factorial(0) matches in the first clause, returning 1 as a result. This means 1*factorial(0) in level 3 returns 1, in level 2 returns 2*1, and in level 1 returns the result of factorial(3), namely 6:

factorial(3).
Level 1: 3 * factorial(3 - 1)           (returns 6)
Level 2:      2 * factorial(2 - 1)      (returns 2)
Level 3:           1 * factorial(1 - 1) (returns 1)
Level 4:                1               (returns 1)

As we mentioned earlier, pattern matching occurs in the function head, and an instance of the variable N is bound after a successful match. Variables are local to each clause. There is no need to allocate or deallocate them; the Erlang runtime system handles that automatically.

Modules

Functions are grouped together in modules. A program will often be spread across several modules, each containing functions that are logically grouped together. Modules consist of files with the .erl suffix, where the file and module names have to be the same. Modules are named using the –module(Name) directive, so in Example 2-2, the demo module would be stored in a file called demo.erl.

Example 2-2. A module example
-module(demo).
-export([double/1]).

% This is a comment.
% Everything on a line after % is ignored.

double(Value) ->
  times(Value, 2).
times(X,Y) ->
  X*Y.

The export directive contains a list of exported functions of the format Function/Arity. These functions are global, meaning they can be called from outside the module. And finally, comments in Erlang start with the percent symbol (%) and span to the end of the line. Make sure you use them everywhere in your code!

Global calls, also called fully qualified function calls, are made by prefixing the module name to the function. So, in Example 2-2, calling demo:double(2) would return 4. Local functions can be called only from within the module. Calling them by prefixing the call with the module name will result in a runtime error. If you were wondering what math:sqrt/1 did in Example 2-1, it calls the sqrt (square root) function from the math module, which comes as part of the Erlang standard distribution.

Functions in Erlang are uniquely identified by their name, their arity, and the module in which they are defined. Two functions in the same module might have the same name but a different arity. If so, they are different functions and are considered unrelated. There is no need to declare functions before they are called, as long as they are defined in the module.

Compilation and the Erlang Virtual Machine

To run functions exported from a module, you have to compile your code, which results in a module.beam file being written in the same directory as the module:

  • If you are using a Unix derivative, start the Erlang shell in the same directory as the source code.

  • In Windows environments, one way to start a werl shell in the right directory is to right-click a .beam file, and in the pop-up window select the Open With option and choose “werl”. This will from now on allow you to get an Erlang shell in the right directory just by double-clicking any .beam file in the same location where you have placed your source code.

With both operating systems, you can otherwise move to the directory by using the cd(Directory) command in the Erlang shell. Once in the directory, you compile the code using c(Module) in the Erlang shell, omitting the erl suffix from the module name. If the code contained no errors, the compilation will succeed.

Large Erlang systems consist of loosely coupled Erlang modules, all compiled on a standalone basis. Once you have compiled your code, look at the source code directory and you will find a file with the same name as the module, but with the .beam suffix. This file contains the byte code that you can call from any other function. The .beam suffix stands for Björn’s Erlang Abstract Machine, an abstract machine on which the compiled code runs.

Once compiled, you need to make a fully qualified function call to run your functions. This is because you are calling the function from outside the module. Calling non-exported functions will result in a runtime error:

1> cd("/home/francesco/examples").
/home/francesco/examples
ok
2> c(demo).
{ok,demo}
3> demo:double(10).
20
4> demo:times(1,2).
** exception error: undefined function demo:times/2

Module Directives

Every module has a list of attributes of the format –attribute(Value). They are usually placed at the beginning of the module, and are recognizable by the – sign in front of the attribute name and the full stop termination. The module attribute is mandatory, and describes the module name. Another attribute we have come across is the export attribute, which takes a list of function/arity definitions.

A useful directive when programming is thecompile(export_all) directive, which at compile time will export all functions defined in the module. Another way of doing this is to specify an option on compiling the file:

c(Mod,[export_all]).

This directive should be used only for testing purposes. Do not do like many others and forget to replace it with an export directive before your code goes into production! The compile directive takes on other options that are useful only in special conditions. If you are curious and want to read more about them, check out the manual page for the compile module.

Another directive isimport(Module, [Function/Arity,...]). It allows you to import functions from other modules and call them locally. Going back to the function area example, including –import(math,[sqrt/1]) as a directive in your module would allow you to rewrite the function clause calculating the area of a triangle call. As a reminder, do not forget to terminate your directives with a full stop:

-import(math, [sqrt/1]).

area({triangle, A, B, C})  ->
  S = (A + B + C)/2,
  sqrt(S*(S-A)*(S-B)*(S-C));

Using the import directive can make your code hard to follow. Someone trying to understand it may at first glance believe sqrt/1 is a local function and unsuccessfully search for it in the module; on the other hand, she can check the directives at the head of the file to see that it is indeed imported. That being said, it’s a convention in the Erlang community to use import sparingly, if at all.

You can make up your own module attributes. Common examples include author(Name) and -date(Date). User-defined attributes can have only one argument (unlike some of the built-in attributes).

All attributes and other module information can be retrieved by calling Mod:module_info/0 or selectively calling the Mod:module_info/1 function. From the shell, you can use the m(Module) command:

5> demo:module_info().
[{exports,[{double,1},{module_info,0},{module_info,1}]},
 {imports,[]},
 {attributes,[{vsn,[74024422977681734035664295266840124102]}]},
 {compile,[{options,[]},
           {version,"4.5.1"},
           {time,{2008,2,25,18,0,28}},
           {source,"/home/francesco/examples/demo.erl"}]}]
6> m(demo).
Module demo compiled: Date: February 25 2008, Time: 18.01
Compiler options:  []
Object file: /home/francesco/examples/demo.beam
Exports:
         double/1
         module_info/0
         module_info/1
ok

If you read through the Erlang libraries, other attributes you will come across will include -behaviour(Behaviour) (U.K. English spelling), -record(Name, Fields), and –vsn(Version). Note that we did not have any vsn attribute in the demo module, but one appeared in the preceding example. When vsn is not defined, the compiler sets it to the MD5 of the module. Note also that the module_info functions appear in the list of exported functions, as they are meant to be accessible outside the module. Do not worry about records, vsn, and behaviour for now, as we cover them in Chapters 7 and 12.

We covered the basics of Erlang in this chapter, and you saw some of its peculiarities: you can assign values to variables, but only once; you can pattern-match against a variable and it may turn into a test for equality with that variable. Other features of the language, such as the module system and the basic types it contains, are more familiar.

We’ll build on this in Chapter 3, where we talk about the details of sequential programming, and then in Chapter 4, where we’ll introduce you to concurrency in Erlang—probably the single most important feature of the language.

Exercises

Exercise 2-1: The Shell

Type in the following Erlang expressions in the shell and study the results. They will show the principles of pattern matching and single-variable assignment described in this chapter. What happens when they execute? What values do the expressions return, and why?

A. Erlang expressions

1 + 1.
[1|[2|[3|[]]]].

B. Assigning through pattern matching

A = 1.
B = 2.
A + B.
A = A + 1.

C. Recursive list definitions

L = [A|[2,3]].
[[3,2]|1].
[H|T] = L.

D. Flow of execution through pattern matching

B = 2.
B = 2.
2 = B.
B = C.
C = B.
B = C.  

E. Extracting values in composite data types through pattern matching

Person = {person, "Mike", "Williams", [1,2,3,4]}.
{person, Name, Surname, Phone} = Person.
Name.

Exercise 2-2: Modules and Functions

Copy the demo module from the example in this chapter. Compile it and try to run it from the shell. What happens when you call demo:times(3,5)? What about double(6) when omitting the module name?

Create a new module called shapes and copy the area function in it. Do not forget to include all the module and export directives. Compile it and run the area function from the shell. When you compile it, why do you get a warning that variable Other is unused? What happens if you rename the variable to _Other?

Exercise 2-3: Simple Pattern Matching

Write a module boolean.erl that takes logical expressions and Boolean values (represented as the atoms true and false) and returns their Boolean result. The functions you write should include b_not/1, b_and/2, b_or/2, and b_nand/2. You should not use the logical constructs and, or, and not, but instead use pattern matching to achieve your goal.

Test your module from the shell. Some examples of calling the exported functions in your module include:

bool:b_not(false) ⇒ true
bool:b_and(false, true) ⇒ false
bool:b_and(bool:b_not(bool:b_and(true, false)), true) ⇒ true

The notation foo(X) ⇒ Y means that calling the function foo with parameter X will result in the value Y being returned. Keep in mind that and, or, and not are reserved words in Erlang, so you must prefix the function names with b_.

Hint: implement b_nand/2 using b_not/1 and b_and/2.



[4] Based on personal experience and confirmed by threads on the Erlang-questions mailing list, this is more common than you might first believe.

[5] Other than winding up people who are on a mission to find design flaws in Erlang.

[6] Variables can also begin with an underscore; these play a role in pattern matching and are discussed in the section Pattern Matching.

[7] We cover side effects and destructive operations later in the book.

[8] Other languages can avoid variable declarations for other reasons. Haskell, for instance, uses a type inference algorithm to deduce types of variables.

[9] Different versions of the Erlang runtime system will format errors differently.

[10] In the case of the triangle, the area is calculated using Heron’s formula where math:sqrt/1 is used to give the square root of a float.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset