Erlang — это в основном функциональный язык, и функции идут рука об руку с неизменными структурами данных, которые передаются в стеке для представления состояния процесса.
Один из самых простых способов представления подобной записи структуры с фиксированным числом полей — это кортеж. Чтобы создать кортеж Покупки, мы можем связать переменные Name, Price и Merchant в единую структуру:
Purchase = {Name, Price, Merchant}.
Повторное извлечение значений выполняется через сопоставление с образцом в структуре кортежа:
doSomething({Name, Price, Merchant}) -> execute(Name).
Однако эта структура подвержена изменениям при модификации. Например:
- добавление поля даты означает, что весь шаблон кода, соответствующий кортежу, должен быть просмотрен и обновлен для добавления другого поля.
- То же самое происходит, если мы удаляем торговца или другое поле, или если мы хотим изменить порядок полей для важности.
- Структура не масштабируется даже до очень немногих полей (скажем, 6), которые вы бы поместили в реляционную таблицу.
Таким образом, для представления C-подобной структуры Erlang предоставляет тип записи , который можно использовать для указания небольшой абстракции над простыми кортежами.
абстракция
Базовое представление записи по-прежнему является кортежем, но доступ к нему осуществляется по имени, а не по индексу. Однако его можно сравнить скорее со структурой C или Java-объектом, чем с картой, поскольку она не является динамической: имена полей и их количество фиксируются во время компиляции.
Записи масштабируют модель кортежа до большего количества полей, отделяя доступные и изменяемые поля от знания чего-либо о несвязанных полях в одной и той же записи (сохраните имя записи).
Существуют встроенные проверки, когда вы сопоставляете шаблон с записями, поэтому при вызове функции, которая ожидает запись с неверным аргументом, вы увидите ошибку function_clause, указывающую на ошибку.
Пачкать руки
Существует эффективный способ изучения новых синтаксисов и моделей кодирования: попробуйте их. Поэтому я отправился с книгой по программированию О’Рейли Эрланга и написал тестовые исследования для собственного синтаксиса Эрланга, который там объясняется.
Определить запись просто: вы должны указать имя и список полей, которые являются атомами. Кроме того, вы можете указать значения по умолчанию для полей; в этом случае я тоже использую атом в качестве значения, но это может быть что угодно, даже другая запись.
-module(records_10). -include_lib("eunit/include/eunit.hrl"). -record(purchase, {name, price, merchant=default}).
Создание записи означает присвоение новой переменной, которая может содержать структуру данных. Синтаксис использует # для идентификации записи с выбранным атомом.
record_creation_test() -> P = #purchase{name="Giorgio", price=10.00, merchant=bakery}, ?assertEqual("Giorgio", P#purchase.name).
Значения по умолчанию, конечно, могут быть опущены:
record_default_value_test() -> P2 = #purchase{name="Giorgio", price=10.00}, ?assertEqual(default, P2#purchase.merchant).
Изменение записи включает только поля, значение которых вы хотите изменить; поскольку переменные Эрланга являются неизменяемыми, вам придется присвоить запись новому идентификатору.
record_modification_test() -> P = #purchase{name="Giorgio", price=10.00}, NewP = P#purchase{price=20.00}, ?assertEqual(#purchase{name="Giorgio", price=20.00}, NewP).
Это должно напомнить вам о классическом шаблоне Value Object : неизменяемые значения, которые генерируют новую структуру после модификации. Однако поведение этих «объектов» оставлено на усмотрение функций, принимающих их в качестве аргументов, и нет инкапсуляции полей и скрытия внутренней структуры (что для многих объектов-значений действительно хорошо).
Сопоставление с образцом может выполняться не только для кортежей и списков, но и для записей:
record_pattern_matching_test() -> P = #purchase{name="Giorgio", price=10.00}, ?assertEqual(2.0, fixed_tax(P)).
fixed_tax(#purchase{price=Price} = Purchase) -> Price * 0.20.
Как и для нескольких функциональных предложений, в шаблонах могут быть указаны ограничения для выбора соответствующего тела:
record_multiple_pattern_matching_test() -> ?assertEqual(2.0, variable_tax(#purchase{name="Giorgio", price=10.00})), ?assertEqual(0.4, variable_tax(#purchase{name="Giorgio", price=10.00, merchant=bakery})).
variable_tax(#purchase{price=Price,merchant=bakery} = Purchase) -> Price * 0.04; variable_tax(#purchase{price=Price} = Purchase) -> Price * 0.20.
Наконец, вы также можете немного разобраться в том, как содержится структура записи, с помощью функции отражения:
record_info_on_fields_test() -> ?assertEqual([name, price, merchant], record_info(fields, purchase)).
Выводы
Программирование — это не только построение полной по Тьюрингу платформы, но и удовлетворение многих нефункциональных требований, таких как способность развивать код за месяцы и годы. Определение записей вместо кортежей помогает в обслуживании и выражении концепций, которые могут оставаться скрытыми в переменной супе.
Весь код этой статьи доступен на Github .