協定

協定 在 Elixir

4 個練習

關於 協定

協定是:

  • 一種實現多型的機制。
  • 用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

請注意,大多數情況下,引發錯誤是比較好的做法,因為很難對_所有_資料型態的協定做出合理的假設。

值得注意的內建協定

透過 GitHub 編輯 連結會在新視窗或分頁中開啟

學習 協定

練習已鎖定

再解鎖 1 個練習,就能練習 協定