Можно ли использовать имя записи в качестве параметра? - PullRequest
7 голосов
/ 05 ноября 2010

Допустим, у меня есть запись:

-record(foo, {bar}).

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

make_record(foo, [bar], ["xyz"])

При реализации такой функции я пробовал это:

make_record(RecordName, Fields, Values) ->
    NewRecord = #RecordName{} %% this line gives me an error: syntax error before RecordName

Можно ли использовать имя записи в качестве параметра?

Ответы [ 3 ]

8 голосов
/ 05 ноября 2010

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

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

-record(some_rec, {a, b}).

make_record(Rec, Values) ->
    list_to_tuple([Rec | Values]).

test() ->
    R = make_record(some_rec, ["Hej", 5]),  % Dynamically create record
    #some_rec{a = A, b = B} = R,            % Access it using record syntax
    io:format("a = ~p, b = ~p~n", [A, B]).  

Или, если во время компиляции составляется список всех записей, которые должна быть в состоянии построить функция, вы также можете использовать имена полей:

%% List of record info created with record_info macro during compile time
-define(recs, 
    [
     {some_rec, record_info(fields, some_rec)}
    ]).

make_record_2(Rec, Fields, Values) ->
    ValueDict = lists:zip(Fields, Values),

    % Look up the record name and fields in record list
    Body = lists:map(
         fun(Field) -> proplists:get_value(Field, ValueDict, undefined) end,
         proplists:get_value(Rec, ?recs)),

    list_to_tuple([Rec | Body]).

test_2() ->
    R = make_record_2(some_rec, [b, a], ["B value", "A value"]),
    #some_rec{a = A, b = B} = R,
    io:format("a = ~p, b = ~p~n", [A, B]).

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

Другими полезными конструкциями, которые следует учитывать при динамической работе с записями, является выражение #some_rec.a, которое оценивает индекс поля a в some_rec s, и функция element(N, Tuple), которая дает кортеж и index возвращает элемент в этом индексе.

4 голосов
/ 05 ноября 2010

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

Вы также можете использовать магию парс-преобразования (см. exprecs ) для создания конструкторов записей и методов доступа, но, похоже, этот дизайн идет не в ту сторону. Если вам нужно динамически создавать подобные записи записи, вы можете вместо этого использовать некоторые структуры, такие как ключ-значение список с или dict с.

0 голосов
/ 22 мая 2015

Чтобы охватить все случаи: Если у вас есть поля и значения, но они не обязательно имеют их в правильном порядке, вы можете заставить свою функцию получить результат в record_info(fields, Record), с Recordбыть атомом записи, которую вы хотите сделать.Тогда у него будут упорядоченные имена полей для работы.А запись - это просто кортеж с именем атома в первом слоте, так что вы можете создать его таким образом.Вот как я строю произвольную поверхностную запись из строки JSON (не полностью протестирована и не оптимизирована, но протестирована и работает):

% Converts the given JSON string to a record
% WARNING: Only for shallow records. Won't work for nested ones!
%
% Record: The atom representing the type of record to be converted to
% RecordInfo: The result of calling record_info(fields, Record)
% JSON: The JSON string
jsonToRecord(Record, RecordInfo, JSON) ->
   JiffyList = element(1, jiffy:decode(JSON)),
   Struct = erlang:make_tuple(length(RecordInfo)+1, ""),
   Struct2 = erlang:setelement(1, Struct, Record),
   recordFromJsonList(RecordInfo, Struct2, JiffyList).

% private methods

recordFromJsonList(_RecordInfo, Struct, []) -> Struct;
recordFromJsonList(RecordInfo, Struct, [{Name, Val} | Rest]) ->
   FieldNames = atomNames(RecordInfo),
   Index = index_of(erlang:binary_to_list(Name), FieldNames),
   recordFromJsonList(RecordInfo, erlang:setelement(Index+1, Struct, Val), Rest). 

% Converts a list of atoms to a list of strings
%
% Atoms: The list of atoms
atomNames(Atoms) ->
   F = fun(Field) ->
      lists:flatten(io_lib:format("~p", [Field]))
      end,
   lists:map(F, Atoms).

% Gets the index of an item in a list (one-indexed)
%
% Item: The item to search for
% List: The list
index_of(Item, List) -> index_of(Item, List, 1).

% private helper
index_of(_, [], _)  -> not_found;
index_of(Item, [Item|_], Index) -> Index;
index_of(Item, [_|Tl], Index) -> index_of(Item, Tl, Index+1).

Краткое объяснение: JSON представляет несколько пар ключ: значение, соответствующих полю:пары значений в записи, которую мы пытаемся построить.Мы можем не получить пары ключ: значение в правильном порядке, поэтому нам нужен список полей записей, переданных для того, чтобы мы могли вставить значения в их правильные позиции в кортеже.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...