Protokolle sind:
defprotocol.defimpl.Eine große Stärke von Protokollen ist, dass die Implementierung eines Protokolls für einen bestimmten Datentyp nicht zusammen mit der Definition des Datentyps oder der Definition des Protokolls ausgeliefert werden muss. Dadurch lassen sich Bibliotheken schreiben, die ihre Nutzer leicht erweitern können, damit sie mit den eigenen Datentypen der Nutzer funktionieren, ohne die Bibliothek ändern zu müssen.
Protokolle können für alle Elixir-Typen implementiert werden: Tuple, Atom, List, BitString, Integer, Float, Function, PID, Map, Port, Reference, Any und Structs.
Protokolle können für mehrere Typen gleichzeitig implementiert werden:
defimpl Reversible, for: [Map, List] do
def reverse(term) do
Enum.reverse(term)
end
end
Wenn du ein Protokoll für ein Struct direkt in dem Modul implementierst, das das Struct definiert, kannst du das Argument :for weglassen:
defmodule ShoppingCart do
defstruct [:items]
defimpl Reversible do
def reverse(shopping_cart) do
Enum.reverse(shopping_cart)
end
end
end
Any
Standardmäßig löst der Aufruf eines Protokolls für einen Datentyp, für den es nicht implementiert wurde, einen Protocol.UndefinedError-Fehler aus:
to_string({})
# => ** (Protocol.UndefinedError) protocol String.Chars not implemented for {} of type Tuple
# (elixir 1.10.3) lib/string/chars.ex:3: String.Chars.impl_for!/1
# (elixir 1.10.3) lib/string/chars.ex:22: String.Chars.to_string/1
Das lässt sich vermeiden, indem du eine Fallback-Implementierung bereitstellst, die in einem solchen Fall verwendet wird. Dazu setzt du beim Definieren des Protokolls das Modulattribut @fallback_to_any auf true und schreibst eine Implementierung für den Typ Any:
defprotocol Reversible do
@fallback_to_any true
def reverse(term)
end
defimpl Reversible, for: Any do
def reverse(term) do
term
end
end
Beachte, dass es in den meisten Fällen die bessere Wahl ist, einen Fehler auszulösen, weil es schwierig ist, eine sinnvolle Annahme über ein Protokoll für alle Datentypen zu treffen.
Enumerable – die Funktionen aus den Modulen Enum und Stream arbeiten mit Datentypen, die dieses Protokoll implementieren.Collectable – die Funktion Enum.into/2 verwendet dieses Protokoll, um ein Enumerable in eine Collection einzufügen.Inspect – du kannst dieses Protokoll implementieren, um zu ändern, wie Kernel.inspect/2 einen bestimmten Datentyp als String darstellt.String.Chars – du kannst dieses Protokoll implementieren, um zu ändern, wie Kernel.to_string/1 einen bestimmten Datentyp als String darstellt.