Protocols
Protocols are a mechanism to achieve polymorphism in Elixir. Dispatching on a protocol is available to any data type as long as it implements the protocol. Let’s see an example.
In Elixir, only false
and nil
are treated as false. Everything else evaluates to true. Depending on the application, it may be important to specify a blank?
protocol that returns a boolean for other data types that should be considered blank. For instance, an empty list or an empty binary could be considered blanks.
We could define this protocol as follows:
defprotocol Blank do
@doc "Returns true if data is considered blank/empty"
def blank?(data)
end
The protocol expects a function called blank?
that receives one argument to be implemented. We can implement this protocol for different Elixir data types as follows:
# Integers are never blank
defimpl Blank, for: Integer do
def blank?(_), do: false
end
# Just empty list is blank
defimpl Blank, for: List do
def blank?([]), do: true
def blank?(_), do: false
end
# Just empty map is blank
defimpl Blank, for: Map do
# Keep in mind we could not pattern match on %{} because
# it matches on all maps. We can however check if the size
# is zero (and size is a fast operation).
def blank?(map), do: map_size(map) == 0
end
# Just the atoms false and nil are blank
defimpl Blank, for: Atom do
def blank?(false), do: true
def blank?(nil), do: true
def blank?(_), do: false
end
And we would do so for all native data types. The types available are:
Atom
BitString
Float
Function
Integer
List
Map
PID
Port
Reference
Tuple
Now with the protocol defined and implementations in hand, we can invoke it:
iex> Blank.blank?(0)
false
iex> Blank.blank?([])
true
iex> Blank.blank?([1, 2, 3])
false
Passing a data type that does not implement the protocol raises an error:
iex> Blank.blank?("hello")
** (Protocol.UndefinedError) protocol Blank not implemented for "hello"