🎓 A1Learner
🔍
0 of 46 lessons completed

Elixir Protocol Implementations

An implementation is the code that keeps a protocol's promises for one data type. It turns the protocol's list of functions into real behaviour for that type.

A built-in type gets its own implementation

The string implementation counts characters. The list implementation counts items.

defprotocol Sized do
  def size(data)
  def empty?(data)
  def present?(data)
end

defimpl Sized, for: BitString do
  def size(string), do: String.length(string)
  def empty?(string), do: string == ""
  def present?(string), do: string != ""
end

defimpl Sized, for: List do
  def size(list), do: length(list)
  def empty?(list), do: list == []
  def present?(list), do: list != []
end

IO.puts(Sized.size("hello"))
IO.puts(Sized.size([10, 20]))
IO.puts(Sized.empty?(""))
IO.puts(Sized.present?([10]))

A custom struct gets its own implementation

The Shelf struct holds books in a list. Its implementation counts those books.

defmodule Shelf do
  defstruct books: []
end

defprotocol Sized do
  def size(data)
  def empty?(data)
  def present?(data)
end

defimpl Sized, for: Shelf do
  def size(%Shelf{books: books}), do: length(books)
  def empty?(%Shelf{books: books}), do: books == []
  def present?(%Shelf{books: books}), do: books != []
end

shelf = struct(Shelf, books: ["a", "b", "c"])
IO.puts(Sized.size(shelf))
IO.puts(Sized.empty?(shelf))
IO.puts(Sized.present?(shelf))

One protocol covers built-in types and a struct

The same Sized calls work for a string, a list, and a Shelf. Dispatch picks the right code for each value.

defmodule Shelf do
  defstruct books: []
end

defprotocol Sized do
  def size(data)
  def empty?(data)
  def present?(data)
end

defimpl Sized, for: BitString do
  def size(string), do: String.length(string)
  def empty?(string), do: string == ""
  def present?(string), do: string != ""
end

defimpl Sized, for: List do
  def size(list), do: length(list)
  def empty?(list), do: list == []
  def present?(list), do: list != []
end

defimpl Sized, for: Shelf do
  def size(%Shelf{books: books}), do: length(books)
  def empty?(%Shelf{books: books}), do: books == []
  def present?(%Shelf{books: books}), do: books != []
end

shelf = struct(Shelf, books: ["a", "b", "c"])
IO.puts(Sized.size("hello"))
IO.puts(Sized.size([10, 20]))
IO.puts(Sized.size(shelf))

Example

defmodule Shelf do
  defstruct books: []
end

defprotocol Sized do
  def size(data)
  def empty?(data)
  def present?(data)
end

defimpl Sized, for: BitString do
  def size(string), do: String.length(string)
  def empty?(string), do: string == ""
  def present?(string), do: string != ""
end

defimpl Sized, for: List do
  def size(list), do: length(list)
  def empty?(list), do: list == []
  def present?(list), do: list != []
end

defimpl Sized, for: Shelf do
  def size(%Shelf{books: books}), do: length(books)
  def empty?(%Shelf{books: books}), do: books == []
  def present?(%Shelf{books: books}), do: books != []
end

shelf = struct(Shelf, books: ["a", "b"])
IO.puts(Sized.size("hello"))
IO.puts(Sized.size([10, 20, 30]))
IO.puts(Sized.size(shelf))
IO.puts(Sized.empty?(shelf))
IO.puts(Sized.present?(""))

Key points

  • defimpl: defimpl names the protocol and the type it covers.
  • Built-in types: One protocol can have a separate implementation for a string and for a list.
  • Custom struct: A struct gets its own implementation in the same way as a built-in type.
  • Own shape: Each implementation uses its own value shape, so a list counts items and a string counts characters.
  • Complete coverage: Each implementation covers every function the protocol promises.
  • One call: Callers use one protocol name, and dispatch picks the matching implementation.

In this example, the Sized protocol is defined once. Then a string implementation, a list implementation, and a Shelf implementation each cover all three promised functions. Finally the calls show each type using its own implementation, including a Shelf built at the top level with struct.

Try it Yourself

Edit the code, then press Run.

Running is simulated

Exercise

Which code gives the Shelf struct a Sized implementation?