Les behaviours permettent de définir des interfaces (des ensembles de fonctions et de macros) dans un module de behaviour, que différents modules de callback pourront ensuite implémenter. Grâce à cette interface commune, ces modules de callback peuvent être utilisés indifféremment.
Remarque l'orthographe britannique de « behaviours ».
Pour définir un behaviour, on crée un nouveau module et on y spécifie une liste de fonctions qui font partie de l'interface voulue. Chaque fonction doit être définie à l'aide de l'attribut de module @callback. La syntaxe est identique à celle d'une spécification de type de fonction (@spec). Il faut préciser un nom de fonction, une liste de types d'arguments et tous les types de retour possibles.
defmodule Countable do
@callback count(collection :: any) :: pos_integer
end
Pour ajouter un behaviour existant à notre module (créer un module de callback), on utilise l'attribut de module @behaviour. Sa valeur doit être le nom du module de behaviour que l'on ajoute.
Ensuite, il faut définir toutes les fonctions (callbacks) requises par ce module de behaviour. Si l'on implémente le behaviour de quelqu'un d'autre, comme les behaviours Access ou GenServer intégrés à Elixir, on trouvera la liste de tous les callbacks de ce behaviour dans la documentation sur hexdocs.pm.
Un module de callback n'est pas limité à l'implémentation des seules fonctions qui font partie de son behaviour. Un même module peut aussi implémenter plusieurs behaviours.
Pour indiquer de quel behaviour vient chaque fonction, on utilise l'attribut de module @impl devant chaque fonction. Sa valeur doit être le nom du module de behaviour qui définit ce 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
Lorsque l'on définit un behaviour, il est possible de fournir une implémentation par défaut d'un callback. Cette implémentation doit être définie dans l'expression quoted de la macro __using__/1. Pour que les utilisateurs du module de behaviour puissent redéfinir l'implémentation par défaut, on appelle la macro defoverridable/1 après l'implémentation de la fonction. Elle accepte une liste de mots-clés dont les clés sont des noms de fonctions et les valeurs des arités de fonctions.
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
Note que définir des fonctions à l'intérieur de __using__/1 est déconseillé pour toute autre raison que définir des implémentations de callback par défaut, mais tu peux toujours définir des fonctions dans un autre module et les importer dans la macro __using__/1.