プロトコルとは、次のようなものです。
defprotocolで定義します。defimplを使って、特定のデータ型に対して実装します。プロトコルの大きな強みは、特定のデータ型に対するプロトコルの実装を、そのデータ型の定義やプロトコルの定義に含めておく必要がないことです。これにより、ライブラリーを変更しなくても、利用者が独自のデータ型に対応させられるように、ライブラリーを簡単に拡張できます。
プロトコルは、Elixirのすべての型に対して実装できます。Tuple、Atom、List、BitString、Integer、Float、Function、PID、Map、Port、Reference、Any、そして構造体です。
プロトコルは、複数の型に対して一度に実装できます。
defimpl Reversible, for: [Map, List] do
def reverse(term) do
Enum.reverse(term)
end
end
構造体を定義しているモジュールの中で直接その構造体に対するプロトコルを実装する場合は、: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が特定のデータ型を文字列として表現する方法を変更できます。