DEV Community

loading...
Cover image for Elixir difference between Behaviour and Protocol

Elixir difference between Behaviour and Protocol

Luan Gomes
Focusing on development best practices.
Updated on ・3 min read

While building and analyzing code in Elixir, you have probably come across the keywords behavior, impl, defprotocol and defimpl, while searching you may have found a short description that can confuse you about when to use them and their differences, this post brings some examples of when to use them and how to implement them in a real project.

Behaviour

Behaviour is a way to implement a user interface for a module to share its api in a public way, this module must have functions that need to be used in the ones that implement this behaviour.

Let's get to the examples:

Suppose we need to define an interface for our parsers, for this we will create a module:

defmodule Parser do
    @callback parse_ids(map) :: map

    @spec to_downcase(map) :: map
    def to_downcase(attrs) do
        attrs
        |> Map.new(fn {i, v} -> {i, String.downcase(v)} end)
    end
end
Enter fullscreen mode Exit fullscreen mode

In this case we create a Parser module with an @callback and a function that takes a map and transforms its values into downcase.

@callback is the function that should be implemented by all modules that use the Parser behavior.

Let's look at an example of its use.

defmodule Person do
    @behaviour Parser

    @impl Parser
    def parse_ids(attrs) do
        attrs
        |> Map.put_new("name", get_in(attrs, ["user", "name"]))
        |> Map.put_new("email", get_in(attrs, ["user", "email"]))
        |> Parser.to_downcase()
    end
end
Enter fullscreen mode Exit fullscreen mode

Here we use the Parser behaviour to define the parse_ids function, otherwise the compiler will return a warning.

Although not required, the @impl ensures that we are implementing the correct callbacks.

Behaviours are a great way to ensure behavior when we need to export a module's public api, as they have types defined in their construction.

Protocols

In a very direct way, protocols are a way to implement polymorphism in Elixir, we use them when we need a module to have a different behavior depending on the type of the value.

Here are some examples:

defprotocol Document do
    @spec id(any) :: any
    def id(item)

    @spec encode(any) :: map
    def encode(item)
end
Enter fullscreen mode Exit fullscreen mode

Here we define our protocol, so everyone who uses it can create the function with a return that is within the specifications and is for a unique type.

Let's use the protocol.

defmodule Person do
    embedded_schema do
        field :name, :string
        field :social_security_number, :string
    end

    defimpl Document, for: __MODULE__ do
        def id(struct), do: struct.social_security_number     

        def encode(struct) do
            struct
            |> Person.to_map()
        end
    end

    def to_map(struct) do
        %{
        name: struct.name,
        social_security_number: struct.social_security_number
        }
    end
end
Enter fullscreen mode Exit fullscreen mode

Here we define the use of the Document protocol with defimpl, see that we are defining the type, through for: __MODULE__, that way when Document.encode is called, it already knows it will return a map, the definition of which map it will return is in the module specification, very separate responsibilities.

But what is the difference between Behaviour and Protocol?

A good example is given by the creator of the language, José Valim, in this post: https://groups.google.com/forum/#!msg/elixir-lang-talk/S0NlOoc4ThM/J2aD2hKrtuoJ

"""A protocol is indeed a behaviour + dispatching logic.

However I think you are missing the point of behaviours. Behaviours are extremely useful. For example, a GenServer defines a behaviour. A behaviour is a way to say: give me a module as argument and I will invoke the following callbacks on it, which these argument and so on. A more complex example for behaviours besides a GenServer are the Ecto adapters.

However, this does not work if you have a data structure and you want to dispatch based on the data structure. Hence protocols."""

I appreciate everyone who has read through here, if you guys have anything to add, please leave a comment.

Discussion (0)