Erlang for the little ones. Chapter 1: Data Types, Variables, Lists, and Tuples

  • Tutorial

Good afternoon, dear hawkers.

This is the first article in a series. To many, it may seem terribly banal because the basics are discussed here. But for beginners, it will be useful, so you can not do without it. It also draws attention to a couple of interesting and non-obvious points.



To start working with Erlang, in the terminal do:
    $ sudo apt-get install erlang
    $ erl

The interpreter will start, and the examples from the article must be executed in it.
Erlang puts a dot at the end of the expression, not a semicolon, as in most languages. Comments begin with a character % and continue to the end of the line.

So let's get started.

The numbers


Erlang supports two types of numerical variables: integers and floating-point numbers. You can perform mathematical operations on numbers such as 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

Note that the result of the division was a floating point number. Erlang is smart enough to automatically cast numerical variables to the right type.

Erlang also allows you to perform several operations at a time. Mathematical operations obey standard priority rules. Therefore, the result of the expression 2 + 3 * 4. will be 14 , because multiplication has a higher priority than addition. In order to explicitly set the order of calculations, you need to use brackets:
	1> (2 + 3) * 4.
	20
	2> -(10 + 3).
	-13

In addition, you are not obliged to be limited only to the decimal number system. You can use numbers with any base from 2 to 36. For this, the number must be specified in the form Base#Value .
	1> 2#11011.
	27
	2> 10#1198.
	1198
	3> 16#A04F.
	41295
	4> 36#1TA.
	2350

But that is not all. 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 an analog of named constants from other languages, and the value of an atom exactly matches its name. Roughly speaking, an atom is a string that cannot be changed.

Atoms must begin with a lowercase letter and may contain lowercase and uppercase letters, numbers, an underscore ( _ ), and a dog ( @ ). Also, an atom can be enclosed in single quotes and then it can include any characters. And of course, an atom cannot coincide with a reserved word. Therefore, the names of the atoms are 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 not allowed.
	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'


Logical data types and comparison operators


Erlang's boolean data types are two reserved atoms: true and false .
One curious and unobvious fact is connected with this. But more on that later.

The language implements all the basic logical operations such as “and” ( and ), “or” ( or ), “exclusive or” ( xor ) and “negation” ( 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

Operators and and or always calculate the values ​​of expressions on both sides of themselves. Therefore, when executing the code (1 > 2) or (3 < 4). , the values ​​of both expressions will be found, although after calculating the right expression, the result is already known. If you want to avoid this, use the andalso and operators orelse .

To compare the values ​​between themselves, the operators “equal” ( == ), “respectively equal” ( =:= ), “respectively unequally” ( =/= ), “unequally” ( /= ), “less than” ( < ), “less than or equal to” ( =< ), “more” are used ( > ) and "greater than or equal to" ( >= ).
	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 used to program in other languages, then, most likely, you’re used to what is true equal in them 1 , as false well 0 . In Erlang, this rule does not work:
	1> true == 1.
	false
	2> false == 0.
	false
	3> false > 19. %% !!!
	true

Have you noticed the third line? Strange, isn't it? And the thing is that Erlang allows you to compare the values of different types and the comparison is guided by the following rule: число(number) < атом(atom) < ссылка(reference) < функция(fun) < порт(port) < процесс(pid) < кортеж(tuple) < список(list) < битовая строка(bit string) . We said above that true and false are atoms, and it can be seen from the above expression that atoms are “more” than numbers. Therefore, it turns out that false > 19. .

Variables


In pure functional languages ​​(such as Haskell) there are no variables. Erlang allows us to create variables, but with one limitation: the value of a variable can be assigned only once. Reassigning a value will cause an error.

The variable name must begin with an uppercase letter or underscore ( _ ). A variable name can consist of just an underscore. But a variable with that name does not remember the value. Such variables are used for pattern matching (see below for more on this).
	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 a group of variables that are somehow related to each other is more convenient to store together. Erlang provides a construct such as a tuple for this. A tuple has the following form: {Value1, Value2, ..., ValueN} and can contain any number of variables.

Let's look at an example of how tuples can be used. A rather hackneyed example: we need to store information about a point on the coordinate plane (X and Y coordinates). We could get two separate variables and store the coordinates in them, but it's easier to store them together.
	1> MyPoint = {2,5}.
	{2,5}

As indicated above, the size of a tuple is not limited to 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


To extract values ​​from a tuple (and not only for this), pattern matching is used. First, let's look at how the matching operator ( = ) works = . It takes the values ​​on the right and compares them with the variables on the left. Roughly speaking, this is the same assignment from imperative languages ​​with only one difference: only unrelated variables can be compared, i.e. those that have no meaning yet.

Pattern matching is when a "template" is specified instead of a single variable, which must match the data. And if the data matches the template, then the variables from this template will be mapped to the corresponding 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 ​​and is not only used to extract data from tuples. Next, we will use this technique quite often.

We do not always need all the data. For example, we may need only the second value of a triple (a triple is a tuple of three values). That would not "produce" useless fact, we can use a special variable that has been talked about before: _ . Thus, we indicate that in this place of the template there must be some value, but we do not need it. Moreover, the template may have several such variables. Convenient, isn't it?
	1> {_,X,_} = {1,2,3}.
	{1,2,3}
	2> X.
	2


Lists


A list is an analogue of arrays from imperative languages. The list is as follows: [Value1, Value2, ..., ValueN] . List items do not have to be of the same 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 is one strange point:
	1> [100,101,102,103].
	"defg"

Erlang displayed the list as a string. Do not worry. This applies only to its display in the terminal. In fact, our list still contains numbers. This behavior is associated with the characteristics of the origin of the language. Erlang initially had no lines. And to work with them, they used lists that stored character numbers. Fortunately, the language is developing, and today we have the opportunity to work with strings normally.

Lists can be added ( ++ ) and subtracted from each other ( -- ). Remember that these operators are also 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]

Lists can also be compared with each other. For this, standard comparison operators are used. First, the heads of the lists are compared. If they are equal, then tail heads are compared, etc. Lists are compared on the first different items. In the example below, the first list is larger because the first item that is different from the corresponding item in the second list is larger ( 4 > 1 ).
	1> [1,2,3,4,0] > [1,2,3,1,1000,2000,6589].
	true

Lists are divided into two parts: head ( head ) and tail ( tail ). The head is the first element of the list, and the tail is everything else. The tail, in turn, also has a head and tail. When comparing with the sample, an operator is used | to indicate where the boundary between the head and tail passes.
	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 will not constantly set lists manually. This is quite tiring and not at all interesting. Fortunately, the creators of the language are of the same opinion, and therefore Erlang has a tool for automatically creating lists. The principle of its work is best viewed with an example. Let's start by writing a code that automatically compiles a list that will contain numbers from 1 to 10 times 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 has the form [Expr || Item <- SourceList]. Erlang, in turn, takes each element from SourceList and substitutes it into the expression Expr , in place of the variable Item . The result of this expression is added to the resulting list. Simple enough, right?

But the generator in the form in which it is now almost useless. Let's complicate the task. Let's make the generator work only with even numbers from the original list, which are more than 5.
	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 looks like [Expr || Item <- SourceList, Condition1, Condition2, ..., Condition2]. It works exactly like the first option, but now Erlang checks that each element of the source list matches the specified conditions. If an element does not fit at least one, it is skipped.

But that is not all. There may be several source lists. For example, let's write a generator that will return all possible combinations of even numbers from 1 to 5 and odd numbers from 6 to 10. Let the combination be represented by a tuple of two elements - 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 has the form [Expr || Item1 <- SourceList1, Item2 <- SourceList2, ..., ItemN <- SourceListN, Condition1, Condition2, ..., ConditionN] . In this case, Erlang will return the Cartesian product of the original lists (more precisely, their elements that fit the conditions).

List generators are a very powerful tool provided by the language. And we will use it very often.

Conclusion


In this article, we examined the very basics of the language. We figured out what types of data are in the language, how they interact with each other. We also examined such fundamental concepts as pattern matching and list generators.

In the next article, we will look at how to use existing and create new functions. And also work with modules.

Thanks for reading. Have a nice code.