🎓 A1Learner
🔍
0 of 46 lessons completed

Elixir Protocol Fallback

A fallback is a catch-all implementation that handles any type with no specific implementation. It keeps a protocol call working for an unexpected type.

The catch-all handles an uncovered type

Labelled has a string implementation and a catch-all. An integer has no specific implementation, so it reaches the catch-all.

defprotocol Labelled do
  @fallback_to_any true
  def label(value)
end

defimpl Labelled, for: BitString do
  def label(string), do: string
end

defimpl Labelled, for: Any do
  def label(_value), do: "unknown"
end

IO.puts(Labelled.label("hello"))
IO.puts(Labelled.label(42))

A specific implementation still wins

A list has its own implementation. It uses that code even though the catch-all is on.

defprotocol Labelled do
  @fallback_to_any true
  def label(value)
end

defimpl Labelled, for: List do
  def label(list), do: "list of #{length(list)}"
end

defimpl Labelled, for: Any do
  def label(_value), do: "unknown"
end

IO.puts(Labelled.label([1, 2]))
IO.puts(Labelled.label(42))
IO.puts(Labelled.label(:ok))

The catch-all can handle a value generically

The Any code uses inspect, which works for any value. It shows the value without assuming its shape.

defprotocol Labelled do
  @fallback_to_any true
  def label(value)
end

defimpl Labelled, for: BitString do
  def label(string), do: string
end

defimpl Labelled, for: Any do
  def label(value), do: "value: #{inspect(value)}"
end

IO.puts(Labelled.label("hello"))
IO.puts(Labelled.label(42))
IO.puts(Labelled.label(:ok))

Example

defprotocol Labelled do
  @fallback_to_any true
  def label(value)
end

defimpl Labelled, for: BitString do
  def label(string), do: string
end

defimpl Labelled, for: List do
  def label(list), do: "list of #{length(list)}"
end

defimpl Labelled, for: Any do
  def label(value), do: "value: #{inspect(value)}"
end

IO.puts(Labelled.label("hi"))
IO.puts(Labelled.label([1, 2, 3]))
IO.puts(Labelled.label(42))
IO.puts(Labelled.label(:ok))

Key points

  • Fallback switch: @fallback_to_any true inside defprotocol turns the catch-all on.
  • Any implementation: defimpl for Any writes the catch-all code.
  • Specific wins: A type with its own implementation still uses that one, not the catch-all.
  • Generic code: The catch-all code should not assume a value shape, because any type can reach it.
  • Off by default: Without the switch, a type with no implementation has no code to run.

In this example, the Labelled protocol turns the catch-all on. Then a string implementation, a list implementation, and an Any implementation are defined. Finally each call shows the rule: a covered type uses its own code, and an uncovered type reaches the generic catch-all.

Note

Keep the catch-all simple, because it runs for every uncovered type.

Try it Yourself

Edit the code, then press Run.

Running is simulated

Exercise

Labelled has a string implementation, a list implementation, and an Any implementation. What does Labelled.label(42) return when the Any code returns "unknown"?