Статьи

Эрланг: записи

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 .