Tracks
/
Elixir
Elixir
/
Syllabus
/
Protocols
Pr

Protocols in Elixir

4 exercises

About Protocols

Protocols are:

  • A mechanism to achieve polymorphism.
  • Defined with defprotocol.
  • Implemented for a specific data type with defimpl.

A big strength of protocols is that the implementation of a protocol for a specific data type doesn't need to be bundled with the data type's definition or the protocol's definition. This allows for writing libraries that can be easily extended by their users to work with the users' custom data types, without having to modify the library.

Implementing

It is possible to implement protocols for all Elixir types: Tuple, Atom, List, BitString, Integer, Float, Function, PID, Map, Port, Reference, Any, and structs.

Protocols can be implemented for multiple types at once:

defimpl Reversible, for: [Map, List] do
  def reverse(term) do
    Enum.reverse(term)
  end
end

When implementing a protocol for a struct directly inside a module that defines the struct, the :for argument can be omitted:

defmodule ShoppingCart do
  defstruct [:items]

  defimpl Reversible do
    def reverse(shopping_cart) do
      Enum.reverse(shopping_cart)
    end
  end
end

Fallback to Any

By default, invoking a protocol for a data type for which it wasn't implemented will raise a Protocol.UndefinedError error:

to_string({})
# => ** (Protocol.UndefinedError) protocol String.Chars not implemented for {} of type Tuple
#        (elixir 1.10.3) lib/string/chars.ex:3: String.Chars.impl_for!/1
#        (elixir 1.10.3) lib/string/chars.ex:22: String.Chars.to_string/1

It is possible to avoid that by providing a fallback implementation that would be used in such a case. To do that, set the @fallback_to_any module attribute to true when defining the protocol and write an implementation for the Any type:

defprotocol Reversible do
  @fallback_to_any true
  def reverse(term)
end

defimpl Reversible, for: Any do
  def reverse(term) do
    term
  end
end

Note that in most cases, raising an error is the preferable approach because it's difficult to make a reasonable assumption about a protocol for all data types.

Note-worthy built-in protocols

  • Enumerable - the functions from the Enum and Stream modules work with data types implementing this protocol.
  • Collectable - the Enum.into/2 function uses this protocol to insert an enumerable into a collection.
  • Inspect - you can implement this protocol to change how Kernel.inspect/2 represents a specific data type as a string.
  • String.Chars - you can implement this protocol to change how Kernel.to_string/1 represents a specific data type as a string.
Edit via GitHub The link opens in a new window or tab

Learn Protocols

Practicing is locked

Unlock 1 more exercise to practice Protocols