Protocols

Protocols provide polymorphism based on data type (similar to interfaces/type classes).

Protocols provide polymorphism based on data type (similar to interfaces/type classes).

Types: Atom, BitString, Float, Function, Integer, List, Map, PID, Port, Reference, Tuple, Any, plus structs.

defprotocol Size do
  @doc "Returns the size of a data structure"
  def size(data)
end

defimpl Size, for: BitString do
  def size(string), do: byte_size(string)
end

defimpl Size, for: Map do
  def size(map), do: map_size(map)
end

defimpl Size, for: Tuple do
  def size(t), do: tuple_size(t)
end

defimpl Size, for: User do          # implement for a struct
  def size(_), do: 1
end

Size.size("hello")    # 5
Size.size(%{a: 1})    # 1

20.1 Fallback to Any and deriving

defprotocol Describe do
  @fallback_to_any true
  def describe(term)
end
defimpl Describe, for: Any do
  def describe(_), do: "something"
end

20.2 Built-in protocols

Enum / Stream functions Enum.into, for... into: to_string/1, interpolation inspect/1, IEx output

JSON.Encoder (1.18+) / Jason.Encoder

defimpl String.Chars, for: User do
  def to_string(user), do: "#{user.name} <#{user.email}>"
end
"User: #{user}"

20.3 Implementing Enumerable for a custom collection

Protocol consolidation happens in Mix builds for fast dispatch.

defmodule Countdown do
  defstruct [:from]
end

defimpl Enumerable, for: Countdown do
  def reduce(_cd, {:halt, acc}, _f), do: {:halted, acc}
  def reduce(cd, {:suspend, acc}, f), do: {:suspended, acc, &reduce(cd, &1, f)}
  def reduce(%Countdown{from: 0}, {:cont, acc}, _f), do: {:done, acc}
  def reduce(%Countdown{from: n}, {:cont, acc}, f),
    do: reduce(%Countdown{from: n - 1}, f.(n, acc), f)

  def count(%Countdown{from: n}), do: {:ok, n}
  def member?(%Countdown{from: n}, x), do: {:ok, is_integer(x) and x in 1..n}
  def slice(_), do: {:error, __MODULE__}
end

Enum.to_list(%Countdown{from: 3})   # [3, 2, 1]