Elixir Behaviours

in combination with Elixir Interfaces, this is how you get this “shared interface” in functional programming.

Difference with using interface?

Ahh, I was confusing with the GraphQL interface through the Absinthe implementation. In GraphQL (which Absinthe implements in Elixir), interfaces define a shared set of fields that different types can implement.

https://elixirschool.com/en/lessons/advanced/behaviours

Sometimes you want modules to share a public API, the solution for this in Elixir is behaviours. Behaviours perform two primary roles:

  • Defining a set of functions that must be implemented
  • Checking whether that set was actually implemented

This is the behaviour we want to see:

defmodule Example.Worker do
  @callback init(state :: term) :: {:ok, new_state :: term} | {:error, reason :: term}
  @callback perform(args :: term, state :: term) ::
              {:ok, result :: term, new_state :: term}
              | {:error, reason :: term, new_state :: term}
end

Then, we can have the implementation

defmodule Example.Downloader do
  @behaviour Example.Worker
 
  def init(opts), do: {:ok, opts}
 
  def perform(url, opts) do
    url
    |> HTTPoison.get!()
    |> Map.fetch(:body)
    |> write_file(opts[:path])
    |> respond(opts)
  end
 
  defp write_file(:error, _), do: {:error, :missing_body}
 
  defp write_file({:ok, contents}, path) do
    path
    |> Path.expand()
    |> File.write(contents)
  end
 
  defp respond(:ok, opts), do: {:ok, opts[:path], opts}
  defp respond({:error, reason}, opts), do: {:error, reason, opts}
end