2010-11-05 2 views
6

Cho phép nói rằng tôi có hồ sơ:Có thể sử dụng tên bản ghi làm thông số trong Erlang

-record (foo, {bar}).

Điều tôi muốn làm là có thể chuyển tên bản ghi thành một hàm làm tham số và lấy lại bản ghi mới. Chức năng này nên được chung chung để nó có thể chấp nhận bất kỳ bản ghi, một cái gì đó như thế này.

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

Khi thực hiện một chức năng như vậy tôi đã cố gắng này

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

Có thể sử dụng tên kỷ lục như một tham số?

Trả lời

5

Điều này là không thể, vì bản ghi là cấu trúc chỉ thời gian biên dịch. Khi biên dịch chúng được chuyển đổi thành các bộ dữ liệu. Do đó trình biên dịch cần phải biết tên của bản ghi, vì vậy bạn không thể sử dụng một biến.

Bạn cũng có thể sử dụng ma thuật chuyển đổi phân tích cú pháp (xem exprecs) để tạo các nhà xây dựng bản ghi và người truy cập, nhưng thiết kế này dường như đi sai hướng. Nếu bạn cần tự động tạo những thứ giống như bản ghi, bạn có thể sử dụng một số cấu trúc thay thế, như khóa-giá trị list s hoặc dict s.

+2

Chúng được chuyển đổi thành bộ dữ liệu chứ không phải mảng. – ZeissS

+0

Cảm ơn bạn đã sửa. – Zed

+0

cảm ơn bạn, tôi đã học được điều gì đó mới hôm nay – nagaru

7

Vâng, bạn không thể sử dụng cú pháp bản ghi nếu bạn không có quyền truy cập vào bản ghi trong thời gian biên dịch.

Nhưng vì hồ sơ được chỉ đơn giản là chuyển đổi thành các bộ trong biên soạn nó chúng ta thực sự dễ dàng để xây dựng chúng bằng tay:

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

Hoặc, nếu bạn ở thời gian biên dịch tạo ra một danh sách của tất cả các hồ sơ mà các chức năng nên có thể xây dựng, bạn có thể sử dụng tên trường cũng:

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

với phiên bản thứ hai bạn cũng có thể làm một số kiểm tra để chắc chắn rằng bạn đang sử dụng các lĩnh vực ngay, vv

Các cấu trúc hữu ích khác cần ghi nhớ khi làm việc với các bản ghi động là biểu thức #some_rec.a đánh giá chỉ mục của trường a trong some_rec s và hàm element(N, Tuple) cho một bộ và một chỉ mục trả về phần tử trong chỉ mục đó.

+0

Vâng, điều này chắc chắn hoạt động. Câu trả lời được chấp nhận là một chút sai lầm. Có, nó không thể làm * chính xác * những gì OP đã cố gắng để làm, nhưng có cách giải quyết dễ dàng. – sudo

0

Để trang trải tất cả các trường hợp: Nếu bạn có các trường và các giá trị nhưng không nhất thiết phải cho họ theo đúng thứ tự, bạn có thể làm cho chức năng của bạn chụp trong kết quả của record_info(fields, Record), với Record là nguyên tử của hồ sơ bạn muốn làm. Sau đó, nó sẽ có các tên trường được sắp xếp để làm việc. Và một kỷ lục chỉ là một tuple với tên nguyên tử của nó trong khe đầu tiên, vì vậy bạn có thể xây dựng nó theo cách đó. Đây là cách tôi xây dựng một kỷ lục cạn tùy ý từ một chuỗi JSON (không kiểm tra kỹ lưỡng và không khả quan, nhưng thử nghiệm và làm việc):

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

giải thích tóm tắt: Các JSON đại diện cho một số chính: các cặp giá trị tương ứng với trường: các cặp giá trị trong hồ sơ chúng tôi đang cố gắng xây dựng. Chúng tôi có thể không nhận được các cặp khóa: giá trị theo đúng thứ tự, vì vậy chúng tôi cần danh sách các trường bản ghi được chuyển vào để chúng tôi có thể chèn các giá trị vào vị trí chính xác của chúng trong bộ dữ liệu.