Do you want to pick up from where you left of?
Take me there

プロトコル

このレッスンではプロトコルがどんなものなのか、そしてElixirでどのように使うのかを見ていきます。

プロトコルとは何か

プロトコルとは何でしょうか? プロトコルはElixirにおいてポリモルフィズムを獲得する手段です。 Erlangの苦痛の一つは、新しく定義する型のために、既存のAPIを拡張していることです。 Elixirではこれを避けるため、関数はその値の型に基いて、動的にディスパッチされます。 Elixirには数多くのビルトインのプロトコルがあり、例えば String.Chars プロトコルは以前使った to_string/1 関数を担当します。 簡単な例で to_string/1 を更に詳しく見てみましょう。

iex> to_string(5)
"5"
iex> to_string(12.4)
"12.4"
iex> to_string("foo")
"foo"

ご覧の通り、この関数を複数の型で呼び出し、どの型でも動くことが示されています。 to_string/1 をタプル(もしくは String.Chars を実装していない何らかの型)で呼び出すとどうなるでしょうか? 見てみましょう:

to_string({:foo})
** (Protocol.UndefinedError) protocol String.Chars not implemented for {:foo}
    (elixir) lib/string/chars.ex:3: String.Chars.impl_for!/1
    (elixir) lib/string/chars.ex:17: String.Chars.to_string/1

ご覧の通り、タプルには実装が無いのでプロトコル・エラーが発生しました。 次のセクションでは、タプルに String.Chars プロトコルを実装します。

プロトコルを実装する

タプルには to_string/1 はまだ実装されていないことが分かっていますので、追加しましょう。 実装を行うには defimpl でプロトコルを指定し、 :for オプションで型を指定します。 実際どんな風になるのか見てみましょう。

defimpl String.Chars, for: Tuple do
  def to_string(tuple) do
    interior =
      tuple
      |> Tuple.to_list()
      |> Enum.map(&Kernel.to_string/1)
      |> Enum.join(", ")

    "{#{interior}}"
  end
end

IExにこれをコピーして流し込めば、今度はエラーを出さずタプルに対して to_string/1 を呼び出すことが出来るはずです。

iex> to_string({3.14, "apple", :pie})
"{3.14, apple, pie}"

どうやってプロトコルの実装を行えばいいのかはわかりましたが、では、新しいプロトコルを定義するにはどうしたら良いのでしょうか? この例では to_atom/1 を実装してみます。 defprotocol を用いる方法を見ていきましょう。

defprotocol AsAtom do
  def to_atom(data)
end

defimpl AsAtom, for: Atom do
  def to_atom(atom), do: atom
end

defimpl AsAtom, for: BitString do
  defdelegate to_atom(string), to: String
end

defimpl AsAtom, for: List do
  defdelegate to_atom(list), to: List
end

defimpl AsAtom, for: Map do
  def to_atom(map), do: List.first(Map.keys(map))
end

さてプロトコルを定義しましたが、このプロトコルではいくつかの型に対する実装で to_atom/1 関数が要求されます。 それではプロトコルができたので、IExで使ってみましょう。

iex> import AsAtom
AsAtom
iex> to_atom("string")
:string
iex> to_atom(:an_atom)
:an_atom
iex> to_atom([1, 2])
:"\x01\x02"
iex> to_atom(%{foo: "bar"})
:foo

特筆すべきは、構造体の内部にはMapがあるものの、構造体はプロトコルの実装をMapと共有していないという点です。それらは列挙可能ではなく、それらにアクセスもできません。

以上でおわかりのように、プロトコルはポリモルフィズムを獲得するための強力な手段です。

間違いを報告したい、あるいはこのレッスンに貢献したい? このレッスンをGitHubで編集しよう!