2010-11-05 17 views
6

Powiedzmy, że mam rekord:Czy można użyć nazwy rekordu jako parametru w Erlang

-record (foo, {bar}).

Chciałbym przekazać nazwę rekordu do funkcji jako parametru i uzyskać nowy rekord. Funkcja powinna być ogólna, tak aby była w stanie zaakceptować dowolny rekord, coś takiego.

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

Przy realizacji takiej funkcji Próbowałem to

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

Czy można użyć nazwy rekordu jako parametr?

Odpowiedz

5

Nie jest to możliwe, ponieważ rekordy są tylko strukturami kompilowanymi. Podczas kompilacji są konwertowane na krotki. W związku z tym kompilator musi znać nazwę rekordu, więc nie można używać zmiennej.

Można również użyć magii przekształcania parse (zob. exprecs) do tworzenia konstruktorów rekordów i akcesorów, ale ten projekt wydaje się iść w niewłaściwym kierunku. Jeśli chcesz dynamicznie tworzyć elementy podobne do rekordów, możesz użyć niektórych struktur, takich jak klucz-wartość list s lub dict s.

+2

Są one konwertowane na krotki, a nie na tablice. – ZeissS

+0

Dzięki za poprawkę. – Zed

+0

dziękuję, nauczyłem się czegoś nowego dzisiaj – nagaru

7

Cóż, nie możesz użyć składni rekordu, jeśli nie masz dostępu do rekordu podczas kompilacji.

Ale ponieważ rekordy są po prostu przekształcona krotki podczas kompilacji to nam bardzo łatwo skonstruować je ręcznie:

-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]). 

Lub, jeśli w czasie kompilacji zrobić listę wszystkich rekordów, że funkcja powinna być w stanie budowy, można użyć nazwy pól także:

%% 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]). 

z drugiej wersji można też zrobić jakąś weryfikację, aby upewnić się, że używasz odpowiedniego pola itd

Inne użyteczne konstrukcje, o których należy pamiętać podczas dynamicznej pracy z rekordami, to wyrażenie #some_rec.a, które ocenia indeks pola a s, a funkcja element(N, Tuple), która dała krotce i indeksowi zwracać element w tym indeksie.

+0

Tak, to na pewno działa. Przyjęta odpowiedź jest nieco myląca. Tak, nie jest możliwe zrobienie * dokładnie * tego, co OP próbował zrobić, ale są łatwe rozwiązania. – sudo

0

Aby objąć wszystkie przypadki: Jeśli masz pól i wartości, ale nie muszą mieć je w odpowiedniej kolejności, można uczynić funkcja podjąć w wyniku record_info(fields, Record) z Record będąc atom rekordu chcieć zrobić. Następnie będzie mieć uporządkowane nazwy pól do pracy. A rekord to tylko krotka nazwa atomu w pierwszym gnieździe, więc możesz ją zbudować w ten sposób. Oto w jaki sposób zbudować dowolny płytkie rekord z ciągiem JSON (nie dokładnie przetestowane i nie zoptymalizowany, ale przetestowane i działa):

% 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). 

Krótkie wyjaśnienie: JSON reprezentuje kilka kluczowych: par wartości odpowiadającej polu: par wartości w zapisie, który próbujemy zbudować. Możemy nie uzyskać par klucz: wartość we właściwej kolejności, więc potrzebujemy listy pól rekordów przekazanych, abyśmy mogli wstawić wartości do ich prawidłowych pozycji w krotce.

Powiązane problemy