Добрый день, дорогие читатели.
Это первая статья из цикла. Многим она может показаться до ужаса банальной т.к. здесь рассматриваются самые основы. Но для новичков она будет полезной, поэтому без нее обойтись нельзя. Так же здесь обращается внимание на пару интересных и неочевидных моментов.
Я всячески стараюсь проповедовать функциональных подход и делаю все возможное, что бы как можно больше людей прониклось его красотой и лаконичностью. Мы с друзьями хотели перевести на русский замечательную книгу, но нам не удалось договориться с автором. Мы не хотим тягомотины с правами и поэтому оставили это дело.
Вместо этого я хочу написать цикл статей об этом языке, взяв за основу эту книгу. Ну и, как мне кажется, хабр — лучшее место для их публикации. Хочу обратить внимание: эти статьи — не вольный перевод вышеуказанного учебника. Мысли изложенные в книге — это основа, план повествования. Конечно, во многом они будут совпадать, но так же есть и интересные моменты из других публикаций и личного опыта (хоть и скромного).
Что бы начать работать с 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 whit whitespace'.
'Atom whit whitespace'
6> ' Atom whit special charactes #^&?'.
' Atom whit 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}
Сопоставление с образцом — один из самых мощных инструментов функциональных языков и используется не только для извлечения данных из кортежей. Далее, мы будем использовать этот прием довольно часто.
Не всегда нам нужны все данные. К примеру, нам может понадобиться только второе значение тройки (тройка — это кортеж из трех значений). Что бы не «плодить» бесполезные сущности, мы можем использовать cпециальную переменную, о которой говорили раньше: _
. Тем самым мы укажем, что в этом месте шаблона должно быть некое значение, но нам оно не нужно. Причем в шаблоне может быть несколько таких переменных. Удобно, не правда ли?
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,24,27,30]
Наше выражение имеет вид [Exp || Item <- SourceList].
Erlang поочередно берет каждый элемент из SourceList
и подставляет его в выражение Exp
, на место переменной 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]
Теперь наш генератор имеет вид [Exp || 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}]
В самом общем случае генератор имеет вид [Exp || Item1 <- SourceList1, Item2 <- SourceList2, ..., ItemN <- SourceListN, Condition1, Condition2, ..., ConditionN]
. В этом случае Erlang вернет декартово произведение исходных списков (точнее их элементов, которые подойдут под условия).
Генераторы списков — очень мощный инструмент, предоставляемый нам языком. И мы будем очень часто использовать его.
Заключение
В этой статье мы рассмотрели самые основы языка. Мы разобрались с тем какие типы данных есть в языке, как они взаимодействуют между собой. А так же рассмотрели такие фундаментальные понятия, как сопоставление с образцом и генераторы списков.
В следующей статье мы рассмотрим как использовать существующие и создавать новые функции. А так же работу с модулями.
Спасибо за то, что читали. Хорошего вам кода.
P.S.
— Буду очень благодарен за высказанное мнение Приветствуются любые замечания, пожелания, а особенно критика. Об ошибках и опечатках прошу сообщать в личку, а не засорять комментарии.
— В интернете совсем нет подходящих иллюстраций, только из упомянутых книг. Буду рад если кто-нибудь сможет подкинуть парочку подходящих изображений, что бы разнообразить статью.
Кстати, над статьями вместе со мной работает Arherpop. Очень сильно помогает. Если у кого-нибудь есть лишний инвайт, пригласите его. Он будет рад.
Автор: HaruAtari
Иной раз без переменных проблематично.
Возможен вариант эмуляции локальных (в контексте процесса) переменных и массивов с использованием ETS-таблиц.