檢視原始碼 結構體
我們在 前幾章 學習了映射
iex> map = %{a: 1, b: 2}
%{a: 1, b: 2}
iex> map[:a]
1
iex> %{map | a: 3}
%{a: 3, b: 2}
結構體是建立在映射上的延伸,提供編譯時檢查和預設值。
定義結構體
若要定義結構體,使用 defstruct/1
建構
iex> defmodule User do
...> defstruct name: "John", age: 27
...> end
與 defstruct
一起使用的關鍵字清單定義結構體將有哪些欄位及其預設值。結構體採用其定義所在模組的名稱。在上面的範例中,我們定義了一個名為 User
的結構體。
現在,我們可以使用類似於用來建立映射的語法來建立 User
結構體
iex> %User{}
%User{age: 27, name: "John"}
iex> %User{name: "Jane"}
%User{age: 27, name: "Jane"}
結構體提供編譯時保證,只有透過 defstruct
定義的欄位才能存在於結構體中
iex> %User{oops: :field}
** (KeyError) key :oops not found expanding struct: User.__struct__/1
存取和更新結構體
結構體與固定鍵的映射共用相同的語法來存取和更新欄位
iex> john = %User{}
%User{age: 27, name: "John"}
iex> john.name
"John"
iex> jane = %{john | name: "Jane"}
%User{age: 27, name: "Jane"}
iex> %{jane | oops: :field}
** (KeyError) key :oops not found in: %User{age: 27, name: "Jane"}
在使用更新語法 (|
) 時,Elixir 知道不會新增任何新鍵到結構體中,讓底層的映射可以在記憶體中共用其結構。在上面的範例中,john
和 jane
都在記憶體中共用相同的鍵結構。
結構體也可以用於模式比對,用於比對特定鍵的值以及確保比對值與比對值為相同類型的結構體。
iex> %User{name: name} = john
%User{age: 27, name: "John"}
iex> name
"John"
iex> %User{} = %{}
** (MatchError) no match of right hand side value: %{}
結構體在底層是單純的映射
結構體只是具有名為 __struct__
的「特殊」欄位的映射,其中包含結構體的名稱
iex> is_map(john)
true
iex> john.__struct__
User
但是,結構體不會繼承映射所擁有的任何協定。例如,您既無法列舉也無法存取結構體
iex> john = %User{}
%User{age: 27, name: "John"}
iex> john[:name]
** (UndefinedFunctionError) function User.fetch/2 is undefined (User does not implement the Access behaviour)
User.fetch(%User{age: 27, name: "John"}, :name)
iex> Enum.each(john, fn {field, value} -> IO.puts(value) end)
** (Protocol.UndefinedError) protocol Enumerable not implemented for %User{age: 27, name: "John"} of type User (a struct)
結構體與協定一起提供 Elixir 開發人員最重要的功能之一:資料多型性。這就是我們將在下一章探討的內容。
預設值和必要鍵
如果您在定義結構體時未指定預設鍵值,則會假設為 nil
iex> defmodule Product do
...> defstruct [:name]
...> end
iex> %Product{}
%Product{name: nil}
您可以定義一個結構,結合具有明確預設值和隱含 nil
值的欄位。在這種情況下,您必須先指定隱含預設為 nil 的欄位
iex> defmodule User do
...> defstruct [:email, name: "John", age: 27]
...> end
iex> %User{}
%User{age: 27, email: nil, name: "John"}
反向執行此操作會產生語法錯誤
iex> defmodule User do
...> defstruct [name: "John", age: 27, :email]
...> end
** (SyntaxError) iex:107: unexpected expression after keyword list. Keyword lists must always come last in lists and maps.
您也可以透過 @enforce_keys
模組屬性來強制執行在建立結構時必須指定某些鍵
iex> defmodule Car do
...> @enforce_keys [:make]
...> defstruct [:model, :make]
...> end
iex> %Car{}
** (ArgumentError) the following keys must also be given when building struct Car: [:make]
expanding struct: Car.__struct__/1
強制執行鍵提供了一個簡單的編譯時期保證,以協助開發人員建立結構。它不會在更新時強制執行,也不會提供任何形式的值驗證。