Erlang и ограничения записи во время выполнения - PullRequest
9 голосов
/ 15 января 2009

Я занимаюсь разработкой системы Erlang и постоянно сталкиваюсь с проблемами, связанными с тем фактом, что записи являются (почти) макросами препроцессора времени компиляции и что ими нельзя манипулировать во время выполнения ... По сути, я работаю с шаблоном свойств, в котором свойства добавляются во время выполнения к объектам на внешнем интерфейсе (AS3). В идеале я хотел бы отразить это с помощью списка на стороне Erlang, поскольку это фундаментальный тип данных, но затем использование записей в QCL [для запроса таблиц ETS] было бы невозможным, поскольку для их использования я должен конкретно указать, какое свойство записи хочу запросить ... У меня есть по крайней мере 15 столбцов в таблице larges, поэтому перечисление их всех в одном огромном операторе switch (случай X of) просто ужасно.

У кого-нибудь есть идеи, как это элегантно решить? Может быть, некоторые встроенные функции для создания кортежей с соответствующими сигнатурами для использования в сопоставлении с образцом (для QLC)?

спасибо

Ответы [ 3 ]

4 голосов
/ 26 февраля 2009

Звучит так, как будто вы хотите иметь возможность сделать что-то вроде get_record_field(Field, SomeRecord), где Field определяется во время выполнения с помощью, скажем, кода пользовательского интерфейса.

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

Есть несколько решений, которые я использовал или смотрел. Мое решение заключается в следующем: (пример дает во время выполнения доступ к #dns_rec и #dns_rr записям из inet_dns.hrl)

%% Retrieves the value stored in the record Rec in field Field.
info(Field, Rec) ->
    Fields = fields(Rec),
    info(Field, Fields, tl(tuple_to_list(Rec))).

info(_Field, _Fields, []) -> erlang:error(bad_record);
info(_Field, [], _Rec) -> erlang:error(bad_field);
info(Field, [Field | _], [Val | _]) -> Val;
info(Field, [_Other | Fields], [_Val | Values]) -> info(Field, Fields, Values).

%% The fields function provides the list of field positions
%% for all the kinds of record you want to be able to query
%% at runtime. You'll need to modify this to use your own records.
fields(#dns_rec{}) -> fields(dns_rec);
fields(dns_rec) -> record_info(fields, dns_rec);
fields(#dns_rr{}) -> fields(dns_rr);
fields(dns_rr) -> record_info(fields, dns_rr).

%% Turns a record into a proplist suitable for use with the proplists module.
to_proplist(R) ->
    Keys = fields(R),
    Values = tl(tuple_to_list(R)),
    lists:zip(Keys,Values).

Версия этого компилируемого доступна здесь: rec_test.erl


Вы также можете расширить этот динамический поиск полей для динамической генерации спецификаций соответствия для использования с ets:select/2 или mnesia:select/2, как показано ниже:

%% Generates a matchspec that does something like this
%% QLC psuedocode: [ V || #RecordKind{MatchField=V} <- mnesia:table(RecordKind) ]
match(MatchField, RecordKind) ->
    MatchTuple = match_tuple(MatchField, RecordKind),
    {MatchTuple, [], ['$1']}.

%% Generates a matchspec that does something like this
%% QLC psuedocode: [ T || T <- mnesia:table(RecordKind),
%%                        T#RecordKind.Field =:= MatchValue]
match(MatchField, MatchValue, RecordKind) ->
    MatchTuple = match_tuple(MatchField, RecordKind),
    {MatchTuple, [{'=:=', '$1', MatchValue}], ['$$']}.

%% Generates a matchspec that does something like this
%% QLC psuedocode: [ T#RecordKind.ReturnField
%%                   || T <- mnesia:table(RecordKind),
%%                        T#RecordKind.MatchField =:= MatchValue]
match(MatchField, MatchValue, RecordKind, ReturnField) 
  when MatchField =/= ReturnField ->
    MatchTuple = list_to_tuple([RecordKind
           | [if F =:= MatchField -> '$1'; F =:= ReturnField -> '$2'; true -> '_' end
              || F <- fields(RecordKind)]]),
    {MatchTuple, [{'=:=', '$1', MatchValue}], ['$2']}.


match_tuple(MatchField, RecordKind) ->
    list_to_tuple([RecordKind
           | [if F =:= MatchField -> '$1'; true -> '_' end
              || F <- fields(RecordKind)]]).

Ульф Вигер также написал parse_transform, Exprecs , который более или менее делает это для вас автоматически. Я никогда не пробовал, но код Ульфа обычно очень хорош.


1 голос
/ 27 февраля 2009

Я решаю эту проблему (в разработке), используя инструменты преобразования для чтения файлов .hrl и создания вспомогательных функций.

Я написал учебник на нем в Trap Exit .

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

Однако, как только вы попадаете в производство, все меняется! Если ваша запись является основой таблицы (в отличие от определения поля в таблице), то изменение базовой записи сложнее (мягко говоря!).

0 голосов
/ 21 января 2009

Я не уверен, что полностью понимаю вашу проблему, но в большинстве случаев я перешел с записей на проплистов . Они гораздо более гибкие и намного медленнее. Используя (d) ets, я обычно использую несколько полей записей для грубого выбора, а затем проверяю проплисты на остальных записях для детального выбора.

...