jonnifyelixir
F3 / F3.06 / behaviours

F3.06 · part 3 of 3

Behaviours & callbacks

A behaviour is the other half of polymorphism. Where a protocol dispatches on a value's type at runtime, a behaviour is a contract on a module: @callback declares the functions a module must provide, @behaviour opts a module in, and the compiler checks that every callback is implemented.

The portal sends notifications through interchangeable modules — email, SMS, push. A Portal.Notifier behaviour names what each must implement. Choose a module and see whether it satisfies the contract.

A contract the compiler checks

The behaviour declares two callbacks: deliver/2 and valid_target?/1. A module that adopts it must define both. Choose a notifier and see which callbacks it provides — and what the compiler says when one is absent.

The implementing module · select one

MODULE EmailNotifier · @behaviour Portal.Notifier REQUIRED CALLBACKS deliver/2 implemented valid_target?/1 implemented all callbacks implemented

      

The contract is enforced at compile time, on the module — a missing callback is a warning before the code ever runs. That is the mirror image of a protocol, whose dispatch is decided at the call.

Declaring callbacks

@callback gives each required function a typed signature. An implementing module adds @behaviour and marks each definition @impl true, which both documents intent and lets the compiler catch a name or arity that drifts from the contract.

defmodule Portal.Notifier do
  @callback deliver(target :: String.t(), message :: String.t()) :: {:ok, term} | {:error, term}
  @callback valid_target?(target :: String.t()) :: boolean
end

defmodule EmailNotifier do
  @behaviour Portal.Notifier
  @impl true
  def deliver(to, msg), do: {:ok, "email to #{to}"}
  @impl true
  def valid_target?(to), do: String.contains?(to, "@")
end

Two kinds of polymorphism

Choose behaviour by the value passed in, or fix a contract that many modules each fulfil.

In Elixir

Protocols dispatch on a value at runtime; behaviours are a compile-time module contract — and OTP's GenServer and Supervisor are behaviours.

That completes protocols and behaviours: one dispatches on data, the other constrains modules. Next module: F3.07 — Processes & the actor model, where a module adopting a behaviour becomes a running, message-passing process.

References

Primary sources for this lesson, and where it connects in the course.

Sources

Related in this course