Fork me on GitHub

プロトコル

This translation is up to date.

このレッスンではプロトコルがどんなものなのか、そして 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 と共有していないという点です。それらは列挙可能ではなく、それらにアクセスもできません。

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


Contributors

loading...



このページをシェアする