Протоколи:
defprotocol.defimpl.Велика перевага протоколів у тому, що реалізацію протоколу для конкретного типу даних не потрібно додавати ні до визначення цього типу даних, ні до визначення самого протоколу. Це дає змогу писати бібліотеки, які їхні користувачі можуть легко розширювати для роботи зі своїми власними типами даних, не змінюючи саму бібліотеку.
Протоколи можна реалізувати для всіх типів Elixir: Tuple, Atom, List, BitString, Integer, Float, Function, PID, Map, Port, Reference, Any і структур.
Протоколи можна реалізувати одразу для кількох типів:
defimpl Reversible, for: [Map, List] do
def reverse(term) do
Enum.reverse(term)
end
end
Коли ми реалізуємо протокол для структури безпосередньо всередині модуля, який визначає цю структуру, аргумент :for можна не вказувати:
defmodule ShoppingCart do
defstruct [:items]
defimpl Reversible do
def reverse(shopping_cart) do
Enum.reverse(shopping_cart)
end
end
end
Any
Типово виклик протоколу для типу даних, для якого його не реалізовано, призведе до помилки Protocol.UndefinedError:
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
Цього можна уникнути, надавши запасну реалізацію, яку буде використано в такому разі. Для цього під час визначення протоколу встановімо атрибут модуля @fallback_to_any у true і напишімо реалізацію для типу Any:
defprotocol Reversible do
@fallback_to_any true
def reverse(term)
end
defimpl Reversible, for: Any do
def reverse(term) do
term
end
end
Зауважмо, що в більшості випадків краще спричинити помилку, адже важко зробити розумне припущення щодо протоколу для всіх типів даних.
Enumerable - функції з модулів Enum і Stream працюють із типами даних, які реалізують цей протокол.Collectable - функція Enum.into/2 використовує цей протокол, щоб вставити перелічуване значення в колекцію.Inspect - ми можемо реалізувати цей протокол, щоб змінити те, як Kernel.inspect/2 подає конкретний тип даних як рядок тексту (англ. string).String.Chars - ми можемо реалізувати цей протокол, щоб змінити те, як Kernel.to_string/1 подає конкретний тип даних як рядок тексту.