Protocolos
En esta lección daremos un vistazo a los Protocolos, qué son y como los podemos utilizar en Elixir.
¿Qué son los Protocolos?
Entonces, ¿qué son?
Los protocolos son un medio para lograr polimorfismo en Elixir.
Uno de los puntos dolorosos de Erlang es extender un API para tipos nuevos.
Para evitar eso, en Elixir la función es despachada dinámicamente basada en el tipo del valor.
Elixir trae varios protocolos, por ejemplo el protocolo String.Chars
es responsable de la funcion to_string/1
que hemos utilizado previamente.
Demos un vistazo más cercano a to_string/1
con un ejemplo rápido:
iex> to_string(5)
"5"
iex> to_string(12.4)
"12.4"
iex> to_string("foo")
"foo"
Como puedes ver hemos llamado a la función con múltiples tipos y hemos demostrado que funciona con todos.
¿Qué pasaria si llamamos a to_string/1
en tuplas (o en cualquier tipo que no haya implementado String.Chars
)?
Veamos:
to_string({:foo})
** (Protocol.UndefinedError) protocol String.Chars not implemented for {:foo}
(elixir) lib/string/chars.ex:3: String.Chars.impl_for!/1
(elixir) lib/string/chars.ex:17: String.Chars.to_string/1
Como puedes ver obtenemos un error de protocolo ya que no existe la implementación para las tuplas.
En la siguiente sección implementaremos el protocolo String.Chars
para tuplas.
Implementando un protocolo
Vimos que to_string/1
aún no ha sido implementado para tuplas, así que agreguémoslo.
Para crear la implementación utilizaremos defimpl
con nuestro protocolo y agregaremos la opción :for
con nuestro tipo.
Veamos como se podría ver:
defimpl String.Chars, for: Tuple do
def to_string(tuple) do
interior =
tuple
|> Tuple.to_list()
|> Enum.map(&Kernel.to_string/1)
|> Enum.join(", ")
"{#{interior}}"
end
end
Si copiamos eso en IEx deberíamos poder llamar a to_string/1
con una tupla sin tener errores:
iex> to_string({3.14, "apple", :pie})
"{3.14, apple, pie}"
Con eso ahora sabemos implementar un protocolo, pero ¿cómo definimos uno nuevo?
Para nuestro ejemplo implementaremos to_atom/1
.
Veamos como se hace con defprotocol
:
defprotocol AsAtom do
def to_atom(data)
end
defimpl AsAtom, for: Atom do
def to_atom(atom), do: atom
end
defimpl AsAtom, for: BitString do
defdelegate to_atom(string), to: String
end
defimpl AsAtom, for: List do
defdelegate to_atom(list), to: List
end
defimpl AsAtom, for: Map do
def to_atom(map), do: List.first(Map.keys(map))
end
Aquí hemos definido nuestro protocolo y su función esperada to_atom/1
, además de su implementación para algunos tipos.
Ya que tenemos nuestro protocolo, usémoslo en IEx:
iex> import AsAtom
AsAtom
iex> to_atom("string")
:string
iex> to_atom(:an_atom)
:an_atom
iex> to_atom([1, 2])
:"\x01\x02"
iex> to_atom(%{foo: "bar"})
:foo
Es importante notar que aunque por debajo los structs son Maps, no comparten las implementaciones de protocolos que los Maps. Los structs no son enumerables, no pueden ser accedidos.
Como podemos ver, los protocolos son una forma poderosa de lograr polimorfismo.
¿Encontraste un error o quieres contribuir a la lección? ¡Edita esta lección en GitHub!