I behaviour ci permettono di definire delle interfacce (insiemi di funzioni e macro) in un modulo di behaviour che potrà essere implementato in seguito da diversi moduli di callback. Grazie all'interfaccia condivisa, questi moduli di callback possono essere usati in modo intercambiabile.
Nota l'ortografia britannica di «behaviours».
Per definire un behaviour, dobbiamo creare un nuovo modulo e specificare un elenco di funzioni che fanno parte dell'interfaccia desiderata. Ogni funzione va definita usando l'attributo di modulo @callback. La sintassi è identica a quella di una typespec di funzione (@spec). Dobbiamo specificare il nome di una funzione, un elenco di tipi di argomento e tutti i possibili tipi restituiti.
defmodule Countable do
@callback count(collection :: any) :: pos_integer
end
Per aggiungere un behaviour esistente al nostro modulo (creando così un modulo di callback) usiamo l'attributo di modulo @behaviour. Il suo valore deve essere il nome del modulo di behaviour che stiamo aggiungendo.
Poi dobbiamo definire tutte le funzioni (callback) richieste da quel modulo di behaviour. Se stiamo implementando il behaviour di qualcun altro, come i behaviour integrati di Elixir Access o GenServer, troveremo l'elenco di tutti i callback del behaviour nella documentazione su hexdocs.pm.
Un modulo di callback non è limitato a implementare solo le funzioni che fanno parte del suo behaviour. Un singolo modulo può anche implementare più behaviour.
Per indicare da quale behaviour proviene ogni funzione, dobbiamo usare l'attributo di modulo @impl prima di ciascuna funzione. Il suo valore deve essere il nome del modulo di behaviour che definisce questo callback.
defmodule BookCollection do
@behaviour Countable
defstruct [:list, :owner]
@impl Countable
def count(collection) do
Enum.count(collection.list)
end
def mark_as_read(collection, book) do
# other function unrelated to the Countable behaviour
end
end
Quando si definisce un behaviour, è possibile fornire un'implementazione predefinita di un callback. Questa implementazione va definita nell'espressione quotata della macro __using__/1. Per permettere a chi usa il modulo di behaviour di sovrascrivere l'implementazione predefinita, chiama la macro defoverridable/1 dopo l'implementazione della funzione. Accetta una keyword list con i nomi delle funzioni come chiavi e la loro arietà come valori.
defmodule Countable do
@callback count(collection :: any) :: pos_integer
defmacro __using__(_) do
quote do
@behaviour Countable
def count(collection), do: Enum.count(collection)
defoverridable count: 1
end
end
end
Tieni presente che definire funzioni dentro __using__/1 è sconsigliato per qualsiasi scopo diverso dal definire implementazioni predefinite dei callback, ma puoi sempre definire le funzioni in un altro modulo e importarle nella macro __using__/1.