Elixir Optional Callbacks
An optional callback is a behaviour function that an implementing module may leave out. Required callbacks must still be defined, while an optional one is provided only when it fits.
A behaviour marks one callback as optional
Worker needs run and may offer describe. The optional list names describe alone.
defmodule Worker do
@callback run(term()) :: term()
@callback describe() :: String.t()
@optional_callbacks describe: 0
end
IO.inspect(Worker.behaviour_info(:optional_callbacks))
IO.inspect({:run, 1} in Worker.behaviour_info(:callbacks))
IO.inspect({:describe, 0} in Worker.behaviour_info(:callbacks))A minimal module defines only the required callback
MiniWorker defines run and leaves describe out. Its run still works, and the describe check is false.
defmodule Worker do
@callback run(term()) :: term()
@callback describe() :: String.t()
@optional_callbacks describe: 0
end
defmodule MiniWorker do
@behaviour Worker
def run(x), do: x + 10
end
IO.inspect(MiniWorker.run(21))
IO.inspect(function_exported?(MiniWorker, :describe, 0))A caller checks before using an optional function
Helper asks whether describe exists before calling it. A full worker gives its description, and a minimal worker gets a default label.
defmodule Worker do
@callback run(term()) :: term()
@callback describe() :: String.t()
@optional_callbacks describe: 0
end
defmodule FullWorker do
@behaviour Worker
def run(x), do: x * 2
def describe(), do: "full"
end
defmodule MiniWorker do
@behaviour Worker
def run(x), do: x + 10
end
defmodule Helper do
def label(worker) do
if function_exported?(worker, :describe, 0), do: worker.describe(), else: "no description"
end
end
IO.inspect(Helper.label(FullWorker))
IO.inspect(Helper.label(MiniWorker))Example
defmodule Worker do
@callback run(term()) :: term()
@callback describe() :: String.t()
@optional_callbacks describe: 0
end
defmodule FullWorker do
@behaviour Worker
def run(x), do: x * 2
def describe(), do: "full"
end
defmodule MiniWorker do
@behaviour Worker
def run(x), do: x + 10
end
defmodule Runner do
def run_and_label(worker, input) do
label = if function_exported?(worker, :describe, 0), do: worker.describe(), else: "no description"
{worker.run(input), label}
end
end
IO.inspect(Runner.run_and_label(FullWorker, 21))
IO.inspect(Runner.run_and_label(MiniWorker, 21))Key points
- Optional list: @optional_callbacks names the callbacks that an implementing module may omit.
- Required rule: every callback not on the optional list must still be defined.
- Check: function_exported?/3 reports whether a module defines a named function.
- Fallback: a caller can use a default value when the optional function is absent.
- Inspection: behaviour_info with optional_callbacks returns the optional list.
In this example, Worker marks describe as optional, FullWorker defines it, and MiniWorker leaves it out. Runner checks for describe, runs the worker, and pairs each result with its label.
Note
an optional callback still has a @callback definition in the behaviour module.
Try it Yourself
Edit the code, then press Run.
Running is simulated
Exercise
A behaviour marks describe/0 as optional. A module defines only the required callbacks. What happens at compile time?