協定是:
defprotocol定義。defimpl為特定資料型態實作。協定的一大優勢,在於為特定資料型態實作的協定不需要與該資料型態的定義或協定的定義綁在一起。如此一來,你就能寫出讓使用者輕鬆擴充的函式庫,使它們支援使用者自訂的資料型態,而不必修改函式庫本身。
可以為所有 Elixir 型別實作協定:Tuple、Atom、List、BitString、Integer、Float、Function、PID、Map、Port、Reference、Any 以及 struct。
協定可以一次為多個型別實作:
defimpl Reversible, for: [Map, List] do
def reverse(term) do
Enum.reverse(term)
end
end
直接在定義該 struct 的模組內為該 struct 實作協定時,可以省略:for引數:
defmodule ShoppingCart do
defstruct [:items]
defimpl Reversible do
def reverse(shopping_cart) do
Enum.reverse(shopping_cart)
end
end
end
Any
根據預設,對尚未實作某協定的資料型態呼叫該協定,會引發Protocol.UndefinedError錯誤:
to_string({})
# => ** (Protocol.UndefinedError) protocol String.Chars not implemented for {} of type Tuple
# (elixir 1.10.3) lib/string/chars.ex:3: String.Chars.impl_for!/1
# (elixir 1.10.3) lib/string/chars.ex:22: String.Chars.to_string/1
你可以提供一個在這種情況下使用的後備實作來避免這個問題。做法是在定義協定時,把@fallback_to_any 模組屬性設為true,並為Any型別撰寫一個實作:
defprotocol Reversible do
@fallback_to_any true
def reverse(term)
end
defimpl Reversible, for: Any do
def reverse(term) do
term
end
end
請注意,大多數情況下,引發錯誤是比較好的做法,因為很難對_所有_資料型態的協定做出合理的假設。
Enumerable:來自Enum和Stream模組的函式,可與實作此協定的資料型態搭配運作。Collectable:Enum.into/2函式會使用此協定,把可列舉項目插入集合中。Inspect:你可以實作此協定,改變Kernel.inspect/2把特定資料型態表示為字串的方式。String.Chars:你可以實作此協定,改變Kernel.to_string/1把特定資料型態表示為字串的方式。