Protocolos com Elixir
Tags: elixir, erlang, protocols, tutorial
Published: 2020-10-29 13:30:57

Os protocolos são uma forma de alcançar polimorfismo utilizando Elixir. Podemos determinar ações para uma função genérica baseado no tipo de dado inserido.

Digamos que você precisa implementar uma função que será responsável por recuperar o primeiro elemento de uma lista.

Teríamos algo mais ou menos assim:
```
defmodule MyList do
  def first_item([item | _list]), do: item
end
```

Usando este exemplo no IEx, conseguimos visualizar o seu funcionamento:
```
iex> my_list = [1, 2, 3, 4, 5, 6, 7]
[1, 2, 3, 4, 5, 6, 7]
iex> MyList.first_item(my_list)
1
```

Uma vez que criamos este módulo `MyList` e definimos a função para receber uma lista como parâmetro através de _pattern matching_, a sua implementação fica limitada a somente listas não-vazias.

O uso de outro tipo de dado como argumento geraria o seguinte erro:
```
iex> MyList.first_item(%{t: 2, b: 3})
** (FunctionClauseError) no function clause matching in MyList.first_item/1
```

Podemos resolver esse problema de duas maneiras, onde uma delas seria implementar uma nova função com um _guard_ que cumpra os requisitos para Maps:

```
defmodule MyList do
  def first_item([item | _list]), do: item
  def first_item(map) when is_map(map) do
    map
    |> Enum.to_list()
    |> List.first()
  end
end
```

E dessa forma seria possível utilizar maps e listas como argumentos, ex:
```
iex> list = [1,2,3,4,5,6,7,8,9]
[1, 2, 3, 4, 5, 6, 7, 8, 9]
iex> map = %{a: 1, b: 2}
%{a: 1, b: 2}
iex> MyList.first_item(list)
1
iex> MyList.first_item(map)
{:a, 1}
```

Enquanto isso, a segunda opção para resolver este problema seria implementar um protocolo para essa função e garantir que esse protocolo funcione para as seguintes implementações: [Lista, Map]

Mas como fazemos isso?


![dancing cat gif](https://media.giphy.com/media/Mr5yS9nR4kAda/giphy.gif)


# Protocolos e Implementações
Fazendo uma breve analogia a linguagens OO (Orientada a Objetos), podemos ver o **Protocolo** em si como uma Interface, onde iremos definir a assinatura de um método, e as implementações como o famoso @override que usamos para dizer que queremos sobrescrever a implementação de determinada funcionalidade da interface que estamos implementando.

A definição de protocolos é feita através da estrutura `defprotocol`, da seguinte forma:
```
defprotocol MyList do
  def first_item(value)
end
```

Não precisamos dizer como a função `first_item/1` irá funcionar, basta indicar que estamos criando esse protocolo e que ele precisa possuir as devidas implementações para funcionar de acordo com o esperado.


Agora vamos fazer as implementações usando `defimpl` para o nosso caso de aceitar Listas e Maps como argumentos:
```
defimpl MyList, for: List do
  def first_item([item | _list]), do: item
end

defimpl MyList, for: Map do
  def first_item(map) do
    map
    |> Enum.to_list()
    |> List.first()
  end
end
```

# Conclusão

Com **Protocolos** nós podemos definir diferentes tipos de comportamentos para uma determinada função, sempre se baseando no tipo de dado que queremos processar.

Um bom exemplo de cenário real para isso é o próprio Jason que usamos para transformar as respostas da nossa aplicação web.

O mesmo possui um protocolo chamado [Jason.Encoder](https://github.com/michalmuskala/jason/blob/master/lib/encoder.ex), contendo a assinatura do método encode. A partir desta interface podemos implementar diferentes tipos de encoding para json dependendo do tipo de dados que queremos retornar na nossa API.