Erlang for Beginners. Data Types, Variables, Lists and Tuples

Erlang

Dear %username%,

Erlang TutorialIt’s the first article of the series. For many of you it may seem terribly trite as I’ll review the very basis of the subject. But this tutorial is going to be really useful for Erlang beginners. I’ll also dwell on some interesting things that aren’t obvious.

In order to begin your work with Erlang, execute the following in the terminal:

$ sudo apt-get install erlang
$ erl

That will start the Erlang interpreter. You should carry out examples from the article in it. In Erlang, in contrast to most languages, we put a period at the end of expression. Comments begin with % symbol and go on till the end of the line.

So, let’s start.

Numbers

Erlang supports two types of numeric variables: integer and float. We can perform the following mathematical operations: addition, subtraction, multiplication and division.

1> 7 + 3.
10
2> 12 - 4.
8
3> 5 * 2.
10
4> 12 / 6
2.0
5> 7 div 3.
2
6> 7 rem 3.
1

Please note, that a floating-point number was the result of division. Erlang is clever enough to automatically cast numeric variables to the necessary type.

Erlang also allows performing several operations at a time. Mathematical operations conform to the standard priority rules. Therefore, the result of 2 + 3 * 4. expression will be 14, since multiplication has higher priority than addition. Use the brackets to explicitly define the order of calculations:

1> (2 + 3) * 4.
20
2> -(10 + 3).
-13

Besides, you don’t have to restrict yourself to decimal notation only. You can use numbers with any base from 2 to 36. For that purpose, you should indicate the number in the form of Base#Value.

1> 2#11011.
27
2> 10#1198.
1198
3> 16#A04F.
41295
4> 36#1TA.
2350

There’s more to come. In Erlang you can use numbers with different bases in one expression:

1> 2#11011 + 36#1TA.
2377
2> 10#1198 - 16#A04F.
-39841
3> 3#201 * 4#321.
1083

Atoms

Atoms are analogues of constant variables from other languages. The atom value conforms precisely to its name. Roughly speaking, an atom is a string that can not be changed.

Atoms should begin with a lowercase letter and can contain lowercase and uppercase characters, digits, underscore (_) and the “at” sign (@). We can also enclose an atom in single quotes. In this case it will be able to contain any characters. An atom can’t coincide with the reserved word. Therefore, the following atom names are invalid: after and andalso band begin bnot bor bsl bsr bxor case catch cond div end fun if let not of or orelse query receive rem try when xor.

1> atom.
atom
2> otherAtom.
otherAtom
3> atom_with_underscore.
atom_with_underscore
4> one@more@atom.
one@more@atom
5> 'Atom with whitespace'.
'Atom with whitespace'
6> ' Atom with special charactes #^&?'.
' Atom with special charactes #^&?'
7> Atom.
* 1: variable 'Atom' is unbound
8> after.
* 1: syntax error before: 'after'

Boolean Data Types and Comparison operators

Boolean data types in Erlang are the two reserved atoms: true and false. There’s an interesting and not obvious fact connected with it. I’ll tell you about it later.

The language has all the basic logical operations, such as and, or, xor and not:

1> true or false.
true
2> true and false.
false
3> true xor false.
true
4> not false.
true
5> (not (true xor true)) or (false and true).
true

and and or operators always calculate expression values from both sides. So by executing (1 > 2) or (3 < 4). we’ll find the values for both expressions, though after calculating the right expression we already know the result. In order to avoid this, use andalso and orelse operators.

Use the following operators to compare values: ==, =:=, =/=, /=, <, =<, > and >=. If your usual language uses == and != to test for and against equality, Erlang uses =:= and =/=.

1> 2 == 2.0.
true
2> 2 =:= 2.0.
false
3> 3 /= 3.0.
false
4> 3 =/= 3.0.
true
5> 5 > 5.
false
6> 5 =< 5.
true

If you’ve programmed in other languages before, you’re most likely got used that true is equal to 1 and false is equal to . This rule isn’t working in Erlang:

1> true == 1.
false
2> false == 0.
false
3> false > 19. %% !!!
true

Have you paid attention to the third line? A bit strange, isn’t it? The thing is that Erlang allows comparing the values of different types. It does that by the following rule:

number < atom < reference < fun < port < pid < tuple < list < bit string

We’ve mentioned above that true and false are atoms. Now we can see that atoms are greater than numbers. Therefore false > 19..

Variables

There are no variables in pure functional languages (such as Haskell). But Erlang does allow us to create variables, though there’s one restriction: we can assign a value to a variable just once. If we try to do it again (with different value) it will lead to en error.

The variable name should begin with a capital letter or an underscore. The variable name can consist of one underscore only. But a variable with such name doesn’t memorize the value. Such variables can be used to match them with the pattern (find the information on the matter below).

1> Variable = 10 - 7.
3   
2> OtherVariable = 3 * 4.
12  
3> _result = Variable + OtherVariable.
15  
4> _result.
15  
5> Variable = 5.
** exception error: no match of right hand side value 5

Tuples

Sometimes it’s more comfortable to have a group of variables that are connected somehow. For that purpose Erlang provides such construction as tuple. The tuple looks like {Value1, Value2, …, ValueN}. It can contain any amount of variables.

Let’s take a look how we can use tuples. The example is quite simple. We need to store the information about a point in the coordinate space (X and Y coordinates). We could create two different variables and store the coordinates in them. But it’s easier to store them together.

1> MyPoint = {2,5}.
{2,5}

As I’ve already mentioned, the size of a tuple isn’t limited by two values. A tuple can also contain values of different types, including other tuples.

1> MyTuple = {1,myAtom,true,{1,false}}.
{1,myAtom,true,{1,false}}

Pattern Matching

In order to extract values from the tuple (not for this purpose only) we utilize pattern matching. First of all, let’s consider the way the matching operator (=) works. The = operator has the role of comparing values and complaining if they’re different. If they’re the same, it returns the value. What this operator does when mixed with variables is that if the left-hand side term is a variable and it is unbound (has no value associated to it), Erlang will automatically bind the right-hand side value to the variable on the left-hand side. The comparison will consequently succeed and the variable will keep the value in memory.

Pattern matching means that instead of a single variable we indicate a template that has to conform the data. If the data correspond to the template, we’ll confront variables of this template with the appropriate values.

1> {X,Y} = {1,2}.
{1,2}
2> X.
1   
3> Y.
2   
4> Z = {Y,X}.
{2,1}
5> {A,B,C} = {myAtom,true,Z}.
{myAtom,true,{2,1}}
6> A.
myAtom
7> B.
true
8> C.
{2,1}

Pattern matching is one of the most powerful tools of functional languages. We can use it not only to extract data from tuples. We’ll apply this approach quite often.

Sometimes we don’t need all the data. For example, we may need just the second value of a three-tuple. Not to “produce” useful entities we can use a special variable we’ve mentioned before: _. In that way we are going to indicate that there can be several such variables in this template part. Quite convenient, isn’t it?

1> {_,X,_} = {1,2,3}.
{1,2,3}
2> X.
2

Lists

A list is an analogue of arrays in imperative languages. A list looks like the following: [Value1, Value2, …, ValueN]. Its elements shouldn’t necessarily be of one type. One list can contain numbers, atoms, tuples, other lists, etc.

1> [1,2,true,atom,{5,4},[true,false]].
[1,2,true,atom,{5,4},[true,false]].

When working with lists in Erlang, there’s the following strange thing:

1> [100,101,102,103].
"defg"

Erlang printed a list as a string. But you shouldn’t worry. This refers to its displaying in terminal only. Our list actually contains numbers. Such behavior is connected with peculiarities of the language. Initially, there were no strings in Erlang. So lists containing symbol numbers were used for working with it. Fortunately, the language is developing. So today we can work with strings.

We can add (++) and subtract lists from each other (–). You should remember that these operators are also right associative.

1> [1,2,3,4,5] ++ [6,7].
[1,2,3,4,5,6,7]
2> [1,2,3,4,5] -- [2,3].
[1,4,5]
3> [1,2,3] ++ []. 
[1,2,3]
4> [1,2,3,4,5] -- [1,2,3] -- [3].
[3,4,5]
5> [1,2,3] ++ [4,5,6] -- [4,5].
[1,2,3,6]

We can also compare the lists. There are standard comparison operators for that purpose. At first we compare list heads. If they are equal, we compare heads of the tails, etc. The lists are compared by the first differing elements. In the example provided below the first list is «greater», because the first element differing from the corresponding element of the second list is greater (4 > 1).

1> [1,2,3,4,0] > [1,2,3,1,1000,2000,6589].
true

Lists are divided into two parts: head and tail. A head is the first element of the list, a tail is everything else. A tail also has a head and a tail. When matching with the pattern we use | operator to indicate the boundary between the head and the tail

1> [Head|Tail] = [1,2,3,4,5].
[1,2,3,4,5]
2> Head.
1   
3> Tail.
[2,3,4,5]
4> [Second|_] = Tail.
[2,3,4,5]
5> Second.
2

List Generator

Of course, we won’t always define lists manually. It’s quite tiresome and isn’t interesting at all. Fortunately, the language developers share this opinion. Therefore, Erlang has a tool for automatic list creation. It’s better to take a look at it by an example. First of all, let’s write a code that will create a list automatically. The list will contain numbers from 1 to 10, multiplied by 3.

1> [X*3 || X <- [1,2,3,4,5,6,7,8,9,10]].
[3,6,9,12,15,18,21,24,27,30]

Our expression is [Expr || Item < — SourceList]. Erlang takes elements from SourceList one by one and replaces each element to Expr expression instead of Item variable. We’ll add the expression result to the resulting list. Quite simple, isn’t it?

But the generator in its current form is almost useless. Let’s complicate the task. We’ll make the generator work only with even numbers (that are greater than 5) from the source list.

1> [X*3 || X <- [1,2,3,4,5,6,7,8,9,10], X rem 2 =:= 0, X > 5].
[18,24,30]

Now our generator is as follows: [Expr || Item < — SourceList, Condition1, Condition2, …, Condition2]. It operates exactly the same way as the previous variant. But now Erlang controls that each element of the source list fits the stated requirements. If the element doesn’t meet at least one requirement – we skip it.

But that’s not the whole story. There can be several source lists. Let’s write a generator as yet another example. It will return all possible combinations of even numbers from 1 to 5 and odd numbers from 6 to 10. Let’s represent the combination by a two-tuple – a pair.

1> [{X,Y} || X <- [1,2,3,4,5], Y <- [6,7,8,9,10], X rem 2 =:= 0, Y rem 2 =:= 1].
[{2,7},{2,9},{4,7},{4,9}]

In the most general case the generator looks like [Expr || Item1 < — SourceList1, Item2 < — SourceList2, …, ItemN < — SourceListN, Condition1, Condition2, …, ConditionN]. In this case Erlang will return the Cartesian product of the source lists (or rather their elements meeting requirements).

List generator is an extremely powerful tool provided by the language and we’ll use it quite often.

Summary

In the article we’ve reviewed the very basic parts of the language. We’ve considered the data types present in the language as well as the way they interact. We’ve also reviewed such fundamental notions as pattern matching and list generators.

In the next article we’ll consider the way we can use existing functions and create new ones. We’ll also take a look how to work with Erlang modules.

Thank you for reading the article. Have a nice code.

Comments

1,128

Ropes — Fast Strings

Most of us work with strings one way or another. There’s no way to avoid them — when writing code, you’re doomed to concatinate strings every day, split them into parts and access certain characters by index. We are used to the fact that strings are fixed-length arrays of characters, which leads to certain limitations when working with them. For instance, we cannot quickly concatenate two strings. To do this, we will at first need to allocate the required amount of memory, and then copy there the data from the concatenated strings.