Erlang для самых маленьких. Глава 1: Типы данных, переменные, списки и кортежи

Этой статьей я начинаю цикл, в котором постараюсь познакомить вас с таким замечательным языком как Erlang. Это вводная статья и в ней мы разберем базовые типы данных, списки и кортежи. А так же разберемся с сопоставлением с образцом и генераторами списков.

Что бы начать работать с Erlang, в терминале выполните:

$ sudo apt-get install erlang
$ erl

Запустится интерпретатор, в нем и надо выполнять примеры из статьи. В Erlang в конце выражения ставится точка, а не точка с запятой, как в большинстве языков. Комментарии начинаются со знака % и продолжаются до конца строки.

Итак, давайте начнем.

Числа

Erlang поддерживает два типа численных переменных: целочисленные и числа с плавающей точкой. Над числами можно производить такие математические операции как сложение, вычитание, умножение и деление:

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

Обратите внимание, что результатом деления было число с плавающей точкой. Erlang достаточно умен, что бы автоматически приводить численные переменные к нужному типу.

Так же Erlang позволяет производить несколько операций за раз. Математические операции подчиняются стандартным правилам приоритета. Поэтому результатом выражения 2 + 3 * 4. будет 14, потому что умножение имеет более высокий приоритет, чем сложение. Для того, что бы явно задать порядок вычислений нужно использовать скобки:

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

К тому же, вы не обязаны ограничиваться только десятичной системой счисления. Вы можете использовать числа с любым основанием от 2 до 36. Для этого число необходимо указывать в виде Base#Value.

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

Но и это еще не все. В Erlang вы можете использовать числа с разными основаниями в одном выражении:

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

Атомы

Атомы — это аналог именованных констант из других языков, причем значение атома в точности соответствует его названию. Грубо говоря, атом — это строка, которую нельзя изменить.

Атомы должны начинаться со строчной буквы и могут содержать в себе строчные и заглавные буквы, цифры, знак подчеркивания(_) и собаку(@). Так же атом можно заключить в одинарные кавычки и тогда он может включать в себя любые символы. Ну и конечно же атом не может совпадать с зарезервированным словом. Поэтому названия атомов 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'

Логические типы данных и операторы сравнения

Булевы типы данных в Erlang — это два зарезервированных атома: true и false. С этим связан один любопытный и не очевидный факт. Но об этом чуть позже.

В языке реализованы все основные логические операции такие как «и»(and), «или»(or), «исключающее или»(xor) и «отрицание»(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 и or всегда вычисляют значения выражений с обеих сторон от себя. Поэтому при выполнении кода (1 > 2) or (3 < 4). будут найдены значения обоих выражений, хотя после вычисления правого выражения результат уже известен. Если вы хотите избежать этого, используйте операторы andalso и orelse.

Для сравнения значений между собой используются операторы «равно»(==), «соответственно равно»(=:=), «соответственно неравно»(=/=), «неравно»(/=), «меньше»(<), «меньше или равно»(=<), «больше»(>) и «больше или равно»(>=).

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

Если вы раньше программировали на других языках, то, скорее всего, привыкли, что в них true равно 1, а false равно 0. В Erlang это правило не работает:

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

Обратили внимание на третью строку? Странно, неправда ли? А все дело в том, что Erlang позволяет сравнивать значения разных типов и при сравнении руководствуется следующим правилом: число(number) < атом(atom) < ссылка(reference) < функция(fun) < порт(port) < процесс(pid) < кортеж(tuple) < список(list) < битовая строка(bit string). Выше мы говорили, что true и false — атомы, а из приведенного выражения видно, что атомы «больше» чисел. Поэтому и получается, что false > 19.

Переменные

В чистых функциональных языках(таких как Haskell) не существует переменных. Erlang же позволяет нам создавать переменные, но с одним ограничением: значение переменной может быть присвоено только один раз. Повторное присвоение значения вызовет ошибку.

Имя переменной должно начинаться с заглавной буквы или знака подчеркивания(_). Имя переменной может состоять из одного только знака подчеркивания. Но переменная с таким именем не запоминает значение. Такие переменные используются для сопоставления с образцом (об этом смотрите далее).

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

Кортежи

Иногда группу переменных, которые каким-либо образом связаны между собой, удобнее хранить вместе. Для этого Erlang предоставляет такую конструкцию, как кортеж. Кортеж имеет следующий вид: {Value1, Value2, ..., ValueN} и может содержать любое количество переменных.

Давайте рассмотрим на примере, как можно использовать кортежи. Довольно заезженный пример: нам необходимо хранить информацию о точке на координатной плоскости (координаты X и Y). Мы могли бы завести две отдельных переменных и хранить координаты в них, но ведь проще хранить их вместе.

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

Как указано выше, размер кортежа не ограничивается двумя значениями. Так же кортеж может содержать значения разных типов, в том числе и другие кортежи.

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

Сопоставление с образцом

Для извлечения значений из кортежа (и не только для этого) используется сопоставление с образцом. Для начала давайте еще раз разберем, как работает оператор сопоставления(=). Он берет значения справа и сопоставляет их с переменными, находящимися слева. Грубо говоря, это то же самое присваивание из императивных языков с одним лишь отличием: сопоставить возможно только несвязанные переменные, т.е. те у которых еще нет значения.

Сопоставление с образцом — это, когда вместо одиночной переменной указывается «шаблон», который должен соответствовать данным. И если данные соответствуют шаблону, то переменные из этого шаблона будут сопоставлены с соответствующими значениями.

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}

Сопоставление с образцом — один из самых мощных инструментов функциональных языков и используется не только для извлечения данных из кортежей. Далее, мы будем использовать этот прием довольно часто.

Не всегда нам нужны все данные. К примеру, нам может понадобиться только второе значение тройки (тройка — это кортеж из трех значений). Что бы не «плодить» бесполезные сущности, мы можем использовать специальную переменную, о которой говорили раньше: _. Тем самым мы укажем, что в этом месте шаблона должно быть некое значение, но нам оно не нужно. Причем в шаблоне может быть несколько таких переменных. Удобно, не правда ли?

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

Списки

Список — это аналог массивов из императивных языков. Список имеет следующий вид: [Value1, Value2, ..., ValueN]. Элементы списка не обязательно должны быть одного типа. Один список может содержать числа, атомы, кортежи, другие списки и т.д.

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

При работе со списками в Erlang есть один странный момент:

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

Erlang вывел список в виде строки. Не стоит волноваться. Это касается только его отображения в терминале. На самом деле наш список все так же содержит числа. Такое поведение связанно с особенностями происхождения языка. Изначально в Erlang не было строк. И для работы с ними использовали списки, которые хранили номера символов. К счастью, язык развивается и сегодня у нас есть возможность нормально работать со строками.

Списки можно складывать (++) и вычитать друг из друга(--). Помните, что эти операторы тоже правоассоциативны.

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]

Так же списки можно сравнивать между собой. Для этого используются стандартные операторы сравнения. Сначала сравниваются головы списков. Если они равны, то сравниваются головы хвостов и т.д. Списки сравниваются по первым различным элементам. В приведенном ниже примере первый список больше потому, что первый элемент, который отличается от соответствующего элемента второго списка больше (4 > 1).

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

Списки делятся на две части: голову(head) и хвост(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

Генераторы списков

Конечно же мы не будем постоянно задавать списки вручную. Это довольно утомительно и совсем не интересно. К счастью, создатели языка придерживаются такого же мнения и поэтому Erlang имеет инструмент для автоматического создания списков. Принцип его работы лучше всего рассматривать на примере. Давайте для начала напишем код, который автоматически составит список, который будет содержать в себе числа от 1 до 10, умноженные на 3.

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

Наше выражение имеет вид [Expr || Item <- SourceList]. Erlang поочередно берет каждый элемент из SourceList и подставляет его в выражение Expr, на место переменной Item. Результат этого выражения добавляется в результирующий список. Достаточно просто, не правда ли?

Но генератор в том виде, в котором он есть сейчас практически бесполезен. Усложним задачу. Сделаем так, что бы генератор работал только с четными числами из исходного списка, которые больше 5.

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

Теперь наш генератор имеет вид [Expr || Item <- SourceList, Condition1, Condition2, ..., Condition2]. Работает он в точности, как и первый вариант, но теперь Erlang проверяет, чтобы каждый элемент исходного списка подходил под указанные условия. Если элемент не подходит хотя бы под одно — он пропускается.

Но и это еще не все. Исходных списков может быть несколько. Давайте для примера напишем генератор, который вернет все возможные комбинации четных чисел от 1 до 5 и нечетных чисел от 6 до 10. Пусть комбинация будет представлена кортежем из двух элементов — парой.

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}]

В самом общем случае генератор имеет вид [Expr || Item1 <- SourceList1, Item2 <- SourceList2, ..., ItemN <- SourceListN, Condition1, Condition2, ..., ConditionN]. В этом случае Erlang вернет декартово произведение исходных списков (точнее их элементов, которые подойдут под условия).

Генераторы списков — очень мощный инструмент, предоставляемый нам языком. И мы будем очень часто использовать его.

Заключение

В этой статье мы рассмотрели самые основы языка. Мы разобрались с тем, какие типы данных есть в языке, как они взаимодействуют между собой. А так же рассмотрели такие фундаментальные понятия, как сопоставление с образцом и генераторы списков.

В следующей статье мы рассмотрим как использовать существующие и создавать новые функции. А так же работу с модулями.

Коментарии

Используйте Markdown

Спасибо за коментарий!
Ваше сообщение будет доступно после проверки.