В эрланге есть такая синтаксическая форма как записи. Это ни в коем случае не семантическая структура, а просто syntax sugar.


-record(article, {
  id,
  user_id = 0,
  body = <<>>,
  published = false
}).

Хочу немного прояснить как они используются и что это вообще такое для тех, кто ещё только начинает знакомиться с эрлангом.

В эрланге есть два способа описать группу нескольких значений: это список и кортеж. Список идеологически — это неизвестного размера группа однородных для данного куска кода объектов предметной области. Кортеж — это фиксированного размера набор разнотипных значений, объединённых для описания одного объекта из предметной области. Это важно понимать: кортеж — один объект, список — коллекция объектов. Других объединяющих штук в эрланге нет.

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


Object = {5, 124, <<"lalala">>, false},
{Id, UserId, Body, _Published} = Object,
Object2 = {Id, UserId, Body, true}
% или
Object2 = setelement(4, Object, true) 

Так кортежами пользоваться становится неудобно из-за необходимости помнить порядковые номера элементов, поэтому на этапе компиляции существуют записи:


Article = #article{},
{article, undefined, 0, <<>>, false} = Article

При объявлении нового экземпляра записи создаётся кортеж в котором первый элемент равен названию записи, количество остальных элементов равно размеру записи и соответствующие по порядку ячейки проставлены значениями по-умолчанию. Т.е. record — это просто соглашение на этапе компиляции о сопоставлении имён порядковым номерам, т.е. по сути такое же смещение, как и в обычном С-шном struct-е. Проставление флага published будет выглядеть:


Article1 = Article#article{published = true}

Необходимо явно указать компилятору как мы собираемся интерпретировать кортеж Article и указать какой элемент собираемся поправить. Код выше преобразуется во что-то вроде:


article = element(1, Article), % проверка на то, что перед нами #article{}
5 = size(Article),
Article1 = setelement(5, Article, true)

Есть возможность напрямую использовать информацию о смещении элемента:


ets:new(article, [{keypos, #article.id}]),
2 = #article.id

Или


lists:keysearch(Id, #article.id, Articles)

Эта же форма записи позволяет вытащить элемент напрямую из кортежа:


io:format("Article author: ~p~n", [Article#article.user_id])

Итого, в обычных манипуляциях с записями их использование превращается в набор element/setelement и матчингов с проверкой типа (первого элемента) и размера кортежа.

Немного по-другому устроена работа с матчингом. Важно понимать, что запись:


Article = #article{published = true}

и


display(#article{published = true} = Article)

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


display(Article) when is_tuple(Article) andalso 
                      element(1, Article) == article andalso 
                      size(Article) == 5 andalso 
                      element(5, Article) == true

Это важно помнить, что бы не допустить такую ошибку:


Pattern = #article{published = true},
receive
  Pattern -> 
     ...
end

Такой код, синтаксически верный, работать будет но совсем не так как можно этого ожидать на первый взгляд: он будет сопоставлять с кортежем {article, undefined, undefined, <<>>, true}.

Иногда записи надо выставлять наружу вместе с API модуля. В этом случае в папке include размещается публичный include файл, например: rtmp.hrl В него кладётся описание записи, а сам файл потом подключается в клиентском коде через include или, что гораздо лучше, через include_lib.

Sidebar