Protocols vs. Behaviours in Elixir: Additional Thoughts
Tags: erlang, elixir, protocol, behaviour
Published: 2021-08-09 14:14:21

---
title: Protocols vs. Behaviours in Elixir: Additional Thoughts
published: true
description: 
tags: erlang, elixir, protocol, behaviour
//cover_image: https://direct_url_to_image.jpg
---

# Initial Thoughts

Recently I have read a fantastic [article](https://yiming.dev/blog/2021/07/18/stop-using-behaviour-to-define-interfaces-use-protocol/) by [Yiming Chen](https://yiming.dev/) about Protocols vs. Behaviours in Elixir.

I want to thank the author for the great article — it clarifies a lot and strictly formulates some ideas that I couldn't acquire by myself.

Here I want to add some thoughts about the use cases of Protocols vs. Behaviours, precisely why we often still use Behaviours instead of Protocols.

My personal **informal** point is the following: 

>Protocols are suitable for pure interfaces (as stated in the article) of **pure data structures**.

When we try to use Protocols with something that deals with side effects, namely processes and message sending, we often start to feel awkward.

The reason is that things with side effects are built with OTP pieces (`GenServer`'s), and:
* we feel comfortable following its original design;
* delayed initialization is essential, and it doesn't look fine with Protocols.

# Illustrations

Let me illustrate that.

I develop a small library [SMPPEX](https://github.com/funbox/smppex), and there I have a module and Behaviour named `Session`. It's only important that it wraps and extends `GenServer`, i.e.
* It specifies callbacks, such as `init`, `handle_call`, ..., and some own ones: `handle_pdu`, etc. They operate as usual: `init` initializes some state, and then it is passed to the callbacks.
* It implements interface functions such as `Session.start_link`, `Session.call`, `Session.cast`, as well as `Session.send_pdu`, etc.

## Naive Design

When I first tried to implement this module, I tried to apply the idea of Protocols.

I made a Protocol, like `SessionState`, which specified callbacks:

```elixir
defprotocol SessionState do
  def handle_call(st, from, message)
  def handle_pdu(st, pdu)
  ...
end
```

To use `Session`, I would do the following.

First, implement `SessionState`.

```elixir
defmodule SessionStateImpl do
  defstruct [...]

  def new(args) do
    ...
  end
end

defimpl SessionState, for: SessionStateImpl do
  def handle_pdu(st, pdu) do
    ...
    {:noreply, new_st}
  end
end
```

Then create an instance and pass it to `Session`:

```elixir
st = SessionStateImpl.new(args)
{:ok, pid} = Session.start_link(st, ...)
```

At first glance, it may seem that everything is excellent, but trying to make a PoC app, I ran into an issue. It is that in OTP *the context* of initialization is essential. 

When implementing `GenServers`, users often like to do something like:

```elixir
def init(opts) do
  ...
  timer = :erlang.start_timer(@interval, self(), :tick)
  ...
end
```

We need to know the session's PID to set up timers or do other useful stuff, like global registration, etc.

But we create `SessionStateImpl` (call `SessionStateImpl.new`) before starting `Session` and don't have a place to do that.

## Probable Improvements

### Solution 1

We may defer `SessionStateImpl` creation and initialization so that `Session` internals could do that. 

```elixir
# st = SessionStateImpl.new(args)
{:ok, pid} = Session.start_link(SessionStateImpl, :new, args, ...)
```

But I don't think that we used Protocol and got rid of passing module and state around to start to pass them again :)

### Solution 2

We may defer initialization and make it be a part of `SessionState` Protocol. The problem is that we can't use Protocol without the underlying struct, so we should do something like:

```elixir
defprotocol SessionState do
  ...
  def init(uninitialized_st, args)
  ...
end

...

defimpl SessionState, for: SessionStateImpl do
  def init(uninitialized_st, args) do
    ...
    # some initialization
    ...
    {:ok, initialized_st}
  end
end

...

st = %SessionStateImpl{}
{:ok, pid} = Session.start_link(st, args, ...)
```

Now we have to pass uninitialized states around — the idea I do not like much.

## Using Behaviours

Playing around with this, I concluded that I was doing something strange and would confuse possible users.

I moved to traditional Behaviours and got the feeling that everything was right :)

## Successful Example of Using Protocols

Once I was developing a system analyzing git commits. The commits were fetched from Bitbucket API and represented huge JSON chunks of data passed around.

I added `Commit` Protocol and implemented it for `APICommit`:

```elixir
defprotocol Commit do
  def author(commit)
  def author_email(commit)
  def added_files(commit)
  ...
end
```

Later I had to combine such commits with ones saved to DB differently. But, thanks to having a Protocol, this logic didn't have to treat them differently after I implemented `Commit` for `DBCommit` data structure.

This case of Protocol usage is an example of dealing with pure data structures, and it proved to be successful.

# Conclusion

The problem of selecting between Behaviours and Protocols in Elixir is exciting and challenging at the same time.

I hope to see more examples of successful and unsuccessful attempts of modeling using Behaviours and Protocols.