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"?