📜 ⬆️ ⬇️

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


Good day, dear habrazhiteli.

This is the first article in the series. To many, it may seem terribly banal because it covers the very basics. But for beginners, it will be useful, so you can not do without it. Also here attention is drawn to a couple of interesting and non-obvious moments.



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

The interpreter will be launched; it is in it that the examples from the article should be executed.
In Erlang, a period is put at the end of the expression, not a semicolon, as in most languages. Comments begin with a % sign and continue to the end of the line.
')
So let's get started.

Numbers


Erlang supports two types of numeric variables: integer and floating point numbers. Mathematical operations such as addition, subtraction, multiplication and division can be performed on numbers:
  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 

Notice that the result of the division was a floating point number. Erlang is smart enough to automatically convert numeric variables to the correct type.

Erlang also allows you to perform several operations at once. Mathematical operations follow 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 required to be limited to only the decimal number system. You can use numbers with any base from 2 to 36. For this, the number must be specified as Base#Value .
  1> 2#11011. 27 2> 10#1198. 1198 3> 16#A04F. 41295 4> 36#1TA. 2350 

But that's not all. In Erlang, you can use numbers with different bases in the same expression:
  1> 2#11011 + 36#1TA. 2377 2> 10#1198 - 16#A04F. -39841 3> 3#201 * 4#321. 1083 


Atoms


Atoms are an analogue of named constants from other languages, and the value of the atom exactly corresponds to its name. Roughly speaking, an atom is a string that cannot be changed.

Atoms must begin with a lowercase letter and can contain lowercase and uppercase letters, numbers, underscore ( _ ) and dog ( @ ). An atom can also 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 atoms of
  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 basic logical operations such as “and” ( and ), “or” ( or ), “excluding or” ( xor ) and “denial” ( 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 

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

To compare the values ​​between themselves, the operators are "equal" ( == ), "equal to each other" ( =:= ), "correspondingly unequal" ( =/= ), "unequal" ( /= ), "less" ( < ), “Less than or equal to” ( =< ), “greater than” ( > ), 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 previously programmed in other languages, then most likely you are used to the fact that in them true is equal to 1 , and false is equal to 0 . In Erlang, this rule does not work:
  1> true == 1. false 2> false == 0. false 3> false > 19. %% !!! true 

Notice the third line? Strange, isn't it? And the thing is that Erlang allows you to compare values ​​of different types and when comparing it is guided by the following rule: (number) < (atom) < (reference) < (fun) < (port) < (pid) < (tuple) < (list) < (bit string) . Above, we said that true and false are atoms, and from the above expression we see 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 a capital letter or an underscore ( _ ). A variable name can consist of just one 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. For this Erlang provides such a construction as a tuple. A tuple has the following form: {Value1, Value2, ..., ValueN} and may contain any number of variables.

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

As stated above, the size of the 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, instead of a single variable, a “pattern” is indicated, which should correspond to the data. And if the data matches the pattern, then the variables from this pattern will be matched with 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 used not only to extract data from tuples. Further, 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). In order not to “produce” useless entities, we can use a special variable, which we talked about earlier: _ . Thus, we indicate that there should be some value in this place of the template, but we do not need it. And in the template there may be several such variables. Convenient, isn't it?
  1> {_,X,_} = {1,2,3}. {1,2,3} 2> X. 2 


Lists


The list is an analogue of arrays from imperative languages. The list has the following form: [Value1, Value2, ..., ValueN] . List items need not 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 thing:
  1> [100,101,102,103]. "defg" 

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

Lists can be added ( ++ ) and subtracted from each other ( -- ). 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] 

Also lists can be compared with each other. For this, standard comparison operators are used. Head lists are compared first. If they are equal, then heads of tails are compared, etc. Lists are compared on the first various elements. In the example below, the first list is larger because the first element that is different from the corresponding element of 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: the head ( head ) and tail ( tail ). The head is the first item in the list, and the tail is the rest. The tail, in turn, also has a head and a tail. For pattern matching, the operator | to indicate where the border between the head and tail is.
  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. It is quite tiring and not at all interesting. Fortunately, the creators of the language hold the same opinion, and therefore Erlang has a tool for automatically creating lists. The principle of its work is best considered by example. Let's first write a code that will automatically make a list that 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]. [Expr || Item <- SourceList]. Erlang takes each item in turn from SourceList and substitutes it in the Expr expression, in place of the Item variable. The result of this expression is added to the result list. Simple enough, isn't it?

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

But that's not all. Source lists can be several. For example, let's write a generator that returns all possible combinations of even numbers from 1 to 5 and odd numbers from 6 to 10. Let the combination be 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] [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 to us by the language. And we will use it very often.

Conclusion


In this article we looked at the very basics of the language. We figured out what data types are in the language, how they interact with each other. We also considered 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. As well as working with modules.

Thanks for reading. Good code to you.

Source: https://habr.com/ru/post/195542/


All Articles