檢視原始碼 記錄

記錄與元組

使用記錄而非元組的主要優勢在於,記錄中的欄位是透過名稱存取的,而元組中的欄位則是透過位置存取的。為了說明這些差異,假設您想要用元組 {Name, Address, Phone} 來表示一個人。

為了編寫處理此資料的函式,請記住以下幾點

  • Name 欄位是元組的第一個元素。
  • Address 欄位是第二個元素。
  • Phone 欄位是第三個元素。

例如,要從包含此元組的變數 P 中提取資料,您可以編寫以下程式碼,然後使用模式匹配來提取相關欄位

Name = element(1, P),
Address = element(2, P),
...

此程式碼難以閱讀和理解,如果元組中元素的編號錯誤,就會發生錯誤。如果欄位的資料表示方式發生變更(透過重新排序、新增或移除欄位),則必須檢查並可能修改所有對 person 元組的參考。

記錄允許透過名稱而非位置來參考欄位。在以下範例中,使用記錄而非元組來儲存資料

-record(person, {name, phone, address}).

這使得可以透過名稱參考記錄的欄位。例如,如果 P 是一個變數,其值為 person 記錄,則以下程式碼會存取記錄的姓名和地址欄位

Name = P#person.name,
Address = P#person.address,
...

在內部,記錄是使用帶標籤的元組表示的

{person, Name, Phone, Address}

定義記錄

本節的幾個範例中使用了以下 person 的定義。包含三個欄位,分別是 namephoneaddressnamephone 的預設值分別是 "" 和 []。address 的預設值是原子 undefined,因為沒有為此欄位提供預設值

-record(person, {name = "", phone = [], address}).

必須在 Shell 中定義記錄,才能在範例中使用記錄語法

> rd(person, {name = "", phone = [], address}).
person

這是因為記錄定義僅在編譯時可用,而不在執行時可用。有關 Shell 中記錄的詳細資訊,請參閱 STDLIB 中的 shell 手冊頁。

建立記錄

新的 person 記錄建立方式如下

> #person{phone=[0,8,2,3,4,3,1,2], name="Robert"}.
#person{name = "Robert",phone = [0,8,2,3,4,3,1,2],address = undefined}

由於省略了 address 欄位,因此使用其預設值。

從 Erlang 5.1/OTP R8B 開始,可以使用特殊欄位 _ 為記錄中的所有欄位設定值。_ 表示「所有未明確指定的欄位」。

範例

> #person{name = "Jakob", _ = '_'}.
#person{name = "Jakob",phone = '_',address = '_'}

它主要用於 ets:match/2mnesia:match_object/3,將記錄欄位設定為原子 '_'。(這是 ets:match/2 中的萬用字元。)

存取記錄欄位

以下範例示範如何存取記錄欄位

> P = #person{name = "Joe", phone = [0,8,2,3,4,3,1,2]}.
#person{name = "Joe",phone = [0,8,2,3,4,3,1,2],address = undefined}
> P#person.name.
"Joe"

更新記錄

以下範例示範如何更新記錄

> P1 = #person{name="Joe", phone=[1,2,3], address="A street"}.
#person{name = "Joe",phone = [1,2,3],address = "A street"}
> P2 = P1#person{name="Robert"}.
#person{name = "Robert",phone = [1,2,3],address = "A street"}

類型測試

以下範例顯示,如果 Pperson 類型的記錄,則 guard 會成功

foo(P) when is_record(P, person) -> a_person;
foo(_) -> not_a_person.

模式匹配

匹配可以與記錄結合使用,如下列範例所示

> P3 = #person{name="Joe", phone=[0,0,7], address="A street"}.
#person{name = "Joe",phone = [0,0,7],address = "A street"}
> #person{name = Name} = P3, Name.
"Joe"

以下函式接收 person 記錄的清單,並搜尋具有特定名稱的人的電話號碼

find_phone([#person{name=Name, phone=Phone} | _], Name) ->
    {found,  Phone};
find_phone([_| T], Name) ->
    find_phone(T, Name);
find_phone([], Name) ->
    not_found.

模式中引用的欄位可以按任何順序給定。

巢狀記錄

記錄中欄位的值可以是記錄的實例。巢狀資料的擷取可以逐步完成,也可以一步完成,如下列範例所示

-record(name, {first = "Robert", last = "Ericsson"}).
-record(person, {name = #name{}, phone}).

demo() ->
  P = #person{name= #name{first="Robert",last="Virding"}, phone=123},
  First = (P#person.name)#name.first.

這裡,demo() 的求值結果為 "Robert"

更長的範例

以下範例中嵌入了註解

%% File: person.hrl

%%-----------------------------------------------------------
%% Data Type: person
%% where:
%%    name:  A string (default is undefined).
%%    age:   An integer (default is undefined).
%%    phone: A list of integers (default is []).
%%    dict:  A dictionary containing various information
%%           about the person.
%%           A {Key, Value} list (default is the empty list).
%%------------------------------------------------------------
-record(person, {name, age, phone = [], dict = []}).
-module(person).
-include("person.hrl").
-compile(export_all). % For test purposes only.

%% This creates an instance of a person.
%%   Note: The phone number is not supplied so the
%%         default value [] will be used.

make_hacker_without_phone(Name, Age) ->
   #person{name = Name, age = Age,
           dict = [{computer_knowledge, excellent},
                   {drinks, coke}]}.

%% This demonstrates matching in arguments

print(#person{name = Name, age = Age,
              phone = Phone, dict = Dict}) ->
  io:format("Name: ~s, Age: ~w, Phone: ~w ~n"
            "Dictionary: ~w.~n", [Name, Age, Phone, Dict]).

%% Demonstrates type testing, selector, updating.

birthday(P) when is_record(P, person) ->
   P#person{age = P#person.age + 1}.

register_two_hackers() ->
   Hacker1 = make_hacker_without_phone("Joe", 29),
   OldHacker = birthday(Hacker1),
   % The central_register_server should have
   % an interface function for this.
   central_register_server ! {register_person, Hacker1},
   central_register_server ! {register_person,
             OldHacker#person{name = "Robert",
                              phone = [0,8,3,2,4,5,3,1]}}.