是否可以使用记录名作为参数?

时间:2010-11-05 05:11:45

标签: erlang

假设我有记录:

-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 个答案:

答案 0 :(得分:8)

如果在编译期间无权访问记录,则无法使用记录语法。

但是因为在编译过程中简单地将记录转换为元组,所以我们很容易手动构建它们:

-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表达式,它计算asome_rec字段的索引和element(N, Tuple)赋予元组和索引的函数返回该索引中的元素。

答案 1 :(得分:4)

这是不可能的,因为记录是仅编译时的结构。在编译时,它们被转换为元组。因此编译器需要知道记录的名称,因此您不能使用变量。

你也可以使用一些解析变换魔法(见exprecs)来创建记录构造函数和访问器,但这种设计似乎走错了方向。 如果您需要动态创建类似记录的内容,则可以使用某些结构,例如键值listdict

答案 2 :(得分:0)

要涵盖所有情况:如果您有字段和值但不一定按正确的顺序排列,则可以使您的函数获取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代表一些关键:对应于我们正在尝试构建的记录中的字段:值对的值对。我们可能无法获得正确顺序的键:值对,因此我们需要传入的记录字段列表,以便我们可以将值插入元组中的正确位置。