読者です 読者をやめる 読者になる 読者になる

もやもやエンジニア

IT系のネタで思ったことや技術系のネタを備忘録的に綴っていきます。フロント率高め。

Elixir 入門 その15 - Protocols

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
  • blank?/1を呼んで確認してみる
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 プロトコルの実装によって実現している。