Protocol
- ProtocolはElixirでポリモーフィズムを実現するための仕組み。プロトコルを使うことでどのようなデータでもやり取りできるようになる
- たとえば Elixirではnilとfalseだけがfalseとして扱われ、それ以外のすべてはtrueと評価されるが、アプリケーションによっては空白をfalseと評価したいケースもないだろうか?このような場合はプロトコルを使うと実現できる。
# Blankというプロトコルを定義
defprotocol Blank do
@doc "Returns true if data is considered blank/empty"
def blank?(data)
end
# プロトコルを実装していく
# Integers are never blank
defimpl Blank, for: Integer do
def blank?(_), do: false
end
# Just empty list is blank
defimpl Blank, for: List do
def blank?([]), do: true
def blank?(_), do: false
end
# Just empty map is blank
defimpl Blank, for: Map do
def blank?(map), do: map_size(map) == 0
end
# Just the atoms false and nil are blank
defimpl Blank, for: Atom do
def blank?(false), do: true
def blank?(nil), do: true
def blank?(_), do: false
end
iex> Blank.blank?(0)
false
iex> Blank.blank?([])
true
iex> Blank.blank?([1, 2, 3])
false
# 実装されていないデータ型を指定すると例外
iex> Blank.blank?("hello")
** (Protocol.UndefinedError) protocol Blank not implemented for "hello"
- このようにargmentの型に応じて動きを変えることを実現している。
Protocols and structs
- Protocolはstructと共に使うと、より効果的に使える
# User structを定義
defmodule User do
defstruct name: "john", age: 27
end
# さきほどのBlank protocolにUserの型を実装
defimpl Blank, for: User do
# nameが入っていなかったら空とする
def blank?(%User{name: ""}), do: true
def blank?(_), do: false
end
# デフォルト値が入っているのでfalse
iex> Blank.blank?(%User{})
false
# nameを"" にするとblank扱いになる
iex> Blank.blank?(%User{name: ""})
true
Falling back to Any
- protocolにanyを実装するとあらゆるデータ型に適用される
# protocol に@fallback_to_anyを追加
defprotocol Blank do
@fallback_to_any true
def blank?(data)
end
# anyを実装
defimpl Blank, for: Any do
def blank?(_), do: false
end
# 上の例では実装されていなかったstringがanyに適用されることがわかる
iex> Blank.blank?("hello")
false
Built-in protocols
- Elixirではすでに組み込み済みの多数のprotocolがある。今までにみてきたEnumモジュールの関数では Enumerable プロトコルを実装したデータ型を引数として受けていた。
- 他にも例えば、to_stringは様々なデータ型を受け取ることができるが、それらはString.Chars プロトコルの実装によって実現している。