Зарегистрируйтесь для доступа к 15+ бесплатным курсам по программированию с тренажером

Эрланг на практике. Вступление. Tипы данных. Эрланг на практике

Видео может быть заблокировано из-за расширений браузера. В статье вы найдете решение этой проблемы.

Вступление

Всем привет :)

Это первый урок курса "Эрланг на практике". И раз вы здесь, значит вы, вероятно, слышали о существовании такого языка программирования. И, наверное, хотите научиться программировать на нем.

С одной стороны, изучить эрланг довольно легко. Сам язык простой, есть несколько хороших книг про него -- бери да учи.

Может быть вы от кого-то слышали, что эрланг можно освоить за 2 недели, и потом можно сразу писать код в реальных проектах. Это почти правда. Действительно можно изучить эрланг по книге за 2 недели. И, действительно, после этого можно писать код в реальных проектах. Но это если вы попали в команду опытных эрланг-разработчиков и пишете код под их присмотром :)

Увы, не всем повезло попасть в такую команду. И есть некоторый разрыв между изучением языка по книгам и реальной работой. Цель моего курса -- закрыть этот разрыв.

Курс не заменяет книги и не отменяет необходимость их читать. Я не дам всю теорию, которую нужно знать, и не очень много внимания уделю основам языка. Во-первых, если задаться целью полностью изложить всю теорию, то курс стал бы слишком большой. А он и так не маленький. Во-вторых, по эрланг есть хорошие книги, написанные хорошими авторами. И мне нет смысла состязаться с ними по глубине знаний и остроте таланта, я проиграю :)

Вместо этого мы сосредоточимся на том, чего нет в книгах -- на практике. Будет много упражнений, несколько небольших проектов, и большой проект в конце курса. Вы будете писать код и получать навыки, необходимые в реальной работе.

Не удивляйтесь, если вопросы и упражнения потребуют знать что-то, о чем я не рассказал. Вы должны сами найти эти знания. Это тоже часть повседневной работы программиста.

Параллельно с изучением курса вам нужно будет прочитать одну из рекомендованных книг.

Изучай Erlang во имя добра!

Фред Хеберт

Переведенная на русский язык

В оригинале: Learn You Some Erlang for Great Good!: A Beginner's Guide Fred Hebert

Бесплатная html версия

Пожалуй, лучшая книга по эрланг. Фред сделал просто эпичный труд, спасибо ему за это. Написано легко, весело, понятно, и про все, про что только можно было написать. (Из-за этого книга получилась очень большая, и читать ее придется долго :)

Программирование в Erlang

Франческо Чезарини, Симон Томпсон

Переведенная на русский язык

В оригинале: Erlang Programming Francesco Cesarini, Simon Thompson

Четкое, последовательное, понятное изложение. Хороший учебник. Несколько сухая, академичная, без шуточек, как у Фреда.

Programming Erlang: Software for a Concurrent World (Pragmatic Programmers)

Joe Armstrong

В оригинале

Автор -- один из создателей языка эрланг. Написано не так сухо, как у Чезарини, более живо, литературно. Немного страдает последовательность изложения, начальные темы идут в довольно странном порядке. Но все равно все мы любим Джо Армстронга :)

Erlang and OTP in Action

Martin Logan, Eric Merritt, Richard Carlsson

В оригинале

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

Можете начинать с любой из этих 4-х книг. Или прочитать 2-3. Они не пересекаются полностью, у каждой есть свои темы и свои фишки.

Чем хорош эрланг

Наверняка вы уже наслышаны о легковесных потоках, передаче сообщений, устойчивости к ошибкам и о горячем обновлении кода.

А если не слышали, то прочитаете об этом на первых страницах вышеуказанных книг, (или в моем блоге http://yzh44yzh.by/ :)

Но, все-таки, кратко:

Виртуальная машина эрланг имеет свою реализацию многопоточности, свои планировщики, работающие поверх процессов операционной системы, и умеющие создавать и управлять десятками и сотнями тысяч потоков. Новый поток стартует за 3-5 микросекунд (не миллисекунд, а микросекунд) и занимает около 2.5 Кб памяти.

У каждого потока своя область памяти и свой сборщик мусора. Нету разделяемой между потоками памяти, которой нужно управлять с помощью блокировок, и нету ошибок типа dead lock и race condition. (На самом деле не все так просто, но на начальных этапах изучения языка можно считать, что этого нет :)

Потоки обмениваются сообщениями друг с другом. При этом данные копируются из памяти одного потока в память другого потока. Таким образом, поток никак не может испортить чужую память. И это значительно упрощает разработку многопоточных приложений.

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

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

Типы данных

Посмотрим, какие встроенные типы данных имеет эрланг.

Их не много, 11 штук. Но я бы все равно их классифицировал. Это не официальная классификация, моя:

  • численные: integer, float;
  • атомы;
  • структуры: list, tuple, map;
  • идентификаторы: pid, port, reference;
  • функции;
  • binary;

Разумеется, на базе встроенных типов данных можно строить любое количество своих. Но сначала познакомимся с базовыми.

integer

Целое число со знаком. Диапазон значений не ограничен. Памяти выделяется столько, сколько нужно, чтобы хранить значение. В большинстве случаев это будет 1 машинное слово (4 байта на 32-х разрядной платформе, 8 байт на 64-х разрядной). Если число большое, и для его хранения не хватает машинного слова, то памяти выделяется больше.

Это удобно для программиста, но не очень эффективно по производительности. Поэтому для эрланг не характерны высокопроизводительные операции с числами.

Числа могут быть представлены в различных системах исчисления, что иногда удобно:

1> 2#101010.
42
2> 8#0677.
447
3> 16#AE.
174

float

Число с плавающей точкой. Как и в большинстве других языков, реализованно по стандарту IEEE 754. Соответственно, занимает 8 байт памяти. Может быть представлено в разных видах.

1> 2.5.
2.5
2> 3.14159.
3.14159
3> -2.3e+6.
-2.3e6
4> 23.56E-27.
2.356e-26

И подвержено потере точности при вычислениях, точно так же, как и в других языках.

5> 0.1 + 0.2.
0.30000000000000004

Таков стандарт IEEE 754 :) Если вы вдруг не знаете, почему так получилось, но хотите узнать, почитайте "Что нужно знать про арифметику с плавающей запятой"

atom

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

Это некие константные значения, которые можно сравнивать друг с другом на предмет совпадения. Собственно, сравнивать -- это единственное, что с ними можно делать.

1> Color1 = green.
green
2> Color2 = red.
red
3> Color3 = green.
green
4> Color1 == Color2.
false
5> Color1 == Color3.
true

При этом они очень широко используются. Но не так, как в примере выше, а в основном для "сопоставления с образцом" (pattern matching). У нас будет отдельный урок, посвященных этой теме.

Пока можно считать, что это некий аналог перечислений (enum), хотя это не совсем точно.

В документации и во всех книгах авторы рассказывают, что атомы хранятся в специальной таблице в памяти и никогда не удаляются оттуда. Поэтому их нельзя генерировать динамически. list_to_atom/1 опасен (не совсем очевидно, но binary_to_term/1 опасен таким же образом). А вот list_to_existing_atom/1 безопасен.

Все-все эрланг разработчики это знают, но все равно иногда это делают. Даже очень опытные. И в один прекрасный момент у них падает нода из-за исчерпания всей памяти ОС. Ну вот, я предупредил, и вы теперь тоже об этом знаете, и никогда не будете так делать :)

Атомы имеют глобальную область видимости (в пределах ноды) и занимают 1 машинное слово в памяти.

Интересно, что в эрланг нет типа данных boolean. А роль boolean значений выполняют атомы true и false. Операции сравнения ==, <, > и т.д. реализованы в языке так, что возвращают эти атомы.

tuple

Кортеж -- структура данных, объединяющая несколько разных значений в одно. Похож на список, но в отличие от списка имеет фиксированную длину.

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

Аналогом кортежа является struct в языке C. И внутри виртуальной машины кортежи именно так и представлены. Общее у кортежа и struct то, что они имеют статичную структуру, заданную на этапе компиляции. В них нельзя добавить новые значения динамически. Разница в том, что в struct поля именованные, а в кортеже анонимные, и определяются их позицией.

Типичный учебный пример: кортеж из двух чисел, задающий координаты точки на плоскости. Или кортеж из трех чисел, задающий координаты точки в пространстве.

1> {5.2, 4.6}.
{5.2,4.6}
2> {5, 7, 9}.
{5,7,9}
3>

Для эрланга характерно на первое место в кортеже ставить атом, поясняющий, какое значение в нем хранится.

3> {point, 5.2, 4.6}.
{point,5.2,4.6}
4> {point3D, 5, 7, 9}.
{point3D,5,7,9}
5> {user, 1, "Bob", 27}.
{user,1,"Bob",27}
{rect,{point,10,10},{point,20,20}}
6> {error, not_found}.
{error, not_found}.

Такие кортежи называются тэгированными (tagged tuple). Это отчасти компенсирует анонимность значений.

Ну и кортежи могут быть вложены друг в друга на несколько уровней.

7> {rect, {point, 10, 10}, {point, 20, 20}}.

Обычно кортежи бывают небольшими -- 2-5 элементов. Для небольших кортежей смысл вложенных в них значений понятен из контекста. Если нужно хранить больше элементов, тогда лучше использовать другую структуру данных -- record. О чем пойдет речь ниже.

list

Список -- основная структура данных. Рабочая лошадка всех функциональных языков. А в некоторых языках (в Lisp) -- это вообще единственная структура данных, заменяющая все остальные :)

Представляет собой однонаправленный связанный список (linked list). Каждый элемент списка состоит из двух частей: некое значение и ссылка на следующий элемент. Список можно обходить по ссылкам от головы (первого элемента) к последнему. В обратном направлении пройти нельзя, потому что обратных ссылок нет.

Хорошее понимание устройства списков и способов их использования очень важно, поэтому следующий урок будет целиком посвящен им. Там я постараюсь объяснить, почему именно однонаправленный связанный список является основной структурой данных, а не массив, и не, например, двунаправленный связанный список.

В эрланге списки могут содержать элементы разных типов:

1> [1,2,3,4,5].
[1,2,3,4,5]
2> [red, green, blue].
[red,green,blue]
3> [4.5, 77, hi, "hello", <<"hello">>].
[4.5,77,hi,"hello",<<"hello">>]

Могут быть вложенными друг в друга на несколько уровней:

4> [1, 2, [3, 4, [5, 6], 7], 8, 9].
[1,2,[3,4,[5,6],7],8,9]

Могут содержать кортежи:

5> [{point, 1, 2}, {point, 3, 4}, {point, 5, 6}].
[{point,1,2},{point,3,4},{point,5,6}]

И кортежи могут содержать внутри себя списки:

6> {field, {width, 10}, {height, 20}, {points, [{point, 1,2}, {point, 3,4}]}}.
{field,{width,10},
    {height,20},
    {points,[{point,1,2},{point,3,4}]}}

С помощью вложенных друг в друга кортежей и списков можно описать любые, даже очень сложные по структуре данные:

{select,["email.email","address.state","account.name"],
        {"user",as,"u"},
        [{joins,[{left,{"email",as,"e"},[{pk,"eid"}]},
                 {left,"address",[{fk,"addr_id"}]},
                 {left,"account",[{pk,"aid"},{fk,"acc_id"}]}]}]

record

Запись (record) -- улучшенный кортеж с именованными полями. Представляет собой синтаксический сахар, добавленный в язык позже. На этапе компиляции превращается в обычный кортеж, и имена полей теряются. То есть, в рантайме записей не существует, только кортежи.

Из-за этого не очень удобно работать с записями в консоли. Приходится либо создавать их вызовом rd() либо загружать определения из внешнего файла.

-record(user, {id = 0,
               name = "",
               age = 0
              }).

Эрланг предлагает синтаксис для создания записей, для чтения и модификации значений полей:

User = #user{}.
User2 = #user{id = 5}.
User3 = #user{id = 5, name = "Bob", age = 24}.
UserId = User#user.id.
UserName = User#user.name.
User4 = User#user{id = 7}.

Ну и, разумеется, записи могут быть вложенны друг в друга, в списки, в кортежи.

Users = [#user{id = 1}, #user{id = 2}, #user{id = 3}].

map

Новый тип данных, появившийся в эрланг с 17-й версии.

До этого в эрланг не было нативной key-value структуры данных, аналогичной словарю (dictionary) в Python или HashMap в Java. Есть различные модули, реализующие подобные структуры (и мы о них будем говорить), но нативной структуры не было.

Разница между map и record/tuple в том, что map позволяет динамически добавлять/удалять элементы.

1> Map1 = #{1 => {user, "Bob"}, 2 => {user, "Bill"}}.
#{1 => {user,"Bob"},2 => {user,"Bill"}}
2> Map2 = Map1#{3 => {user, "Helen"}}.
#{1 => {user,"Bob"},2 => {user,"Bill"},3 => {user,"Helen"}}
3> Map3 = Map2#{1 := {user, "Bob Bobovich"}}.
#{1 => {user,"Bob Bobovich"},2 => {user,"Bill"},3 => {user,"Helen"}}
4> maps:get(1, Map3).
{user,"Bob Bobovich"}
5> maps:get(2, Map3).
{user,"Bill"}
6> maps:get(3, Map3).
{user,"Helen"}

pid, port, reference

Эти типы являются идентификаторами.

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

1> F = fun() -> timer:sleep(5000) end.
#Fun<erl_eval.20.90072148>
2> Pid = spawn(F).
<0.36.0>
3> Pid ! hello.
hello

port является идентификатором специального процесса, связанного с сокетом.

1> gen_tcp:listen(8080, []).
{ok,#Port<0.588>}
2> gen_udp:open(9090).
{ok,#Port<0.593>}

reference является индентификатором общего назначения, который можно использовать по своему усмотрению. Например, как ключ для хранения объекта в ets таблице. Или им можно пометить сообщение, отправленное другому потоку, и ждать ответное сообщение, помеченное тем же ключом.

1> Ref = make_ref().
#Ref<0.0.0.30>
2> Pid = spawn(timer, sleep, [10000]).
<0.36.0>
3> Pid ! {Ref, hello}.
{#Ref<0.0.0.30>,hello}

Эрланг гарантирует, что make_ref при каждом вызове генерирует новый уникальный ключ.

fun

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

1> F = fun(Val) -> Val rem 2 =:= 0 end.
#Fun<erl_eval.6.90072148>
2> lists:filter(F, [1,2,3,4,5,6]).
[2,4,6]

binary

И, наконец, тип binary. Представляет собой просто последовательность байт. Именно в таком виде мы получаем данные из сокета, из файла -- из внешнего мира. Эрланг очень быстро и эффективно работает с binary, и имеет удобные средства для преобразования этих байт в понятные данные.

Например, мощный bit syntax:

8> Val1 = 512.
512
9> Val2 = 768.
768
10> Val3 = 32.
32
11> Bin = <<Val1:32/integer, Val2:16/integer, Val3:8/integer>>.
<<0,0,2,0,3,0,32>>
12> <<Val:32/integer, Rest/binary>> = Bin.
<<0,0,2,0,3,0,32>>
13> Val.
512
14> Rest.
<<3,0,32>>

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

Эрланг умеет эффективно сериализовать и десериализовать свои типы данных в binary. И имеет собственный компактный формат сериализации BERT (binary erlang term storage). И есть библиотеки для многих других языков, реализующие этот формат. Так что его можно брать для любого клиент-серверного приложения.

16> Bin = term_to_binary([{user, "Bob"}, {user, "Bill"}]).
<<131,108,0,0,0,2,104,2,100,0,4,117,115,101,114,107,0,3,
66,111,98,104,2,100,0,4,117,115,101,...>>
17> binary_to_term(Bin).
[{user,"Bob"},{user,"Bill"}]
18> Bin2 = term_to_binary({1,2,3}).
<<131,104,3,97,1,97,2,97,3>>
19> binary_to_term(Bin2).
{1,2,3}

Ну и, разумеется, в эрланг есть библиотеки для работы с другими форматами сериализации. Например, с JSON. Работу с JSON мы будем рассматривать на одном из уроков.


Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты

Об обучении на Хекслете

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff

Используйте Хекслет по-максимуму!

  • Задавайте вопросы по уроку
  • Проверяйте знания в квизах
  • Проходите практику прямо в браузере
  • Отслеживайте свой прогресс

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»