‘Iacta alea est’

Ok Elixir

In previous posts I looked at making the implicit explicit in functional programming using a couple of examples from Elm, Maybe and Result.

The reason Elm can do this with great success is that it is a statically typed language. The compiler will check that the program conforms to and deals with the optionality of Maybe and duality of Result. It will gently nudge us to deal with failure cases explicitly and will not allow us to forget, ErrNothing.

In Elixir, as with Erlang, the language is dynamically typed. It is unable to guarantee a similar level of correctness like Elm. However, unlike conventional Ruby, often held up as the inspiration for Elixir’s approachability at the syntax level, it builds rather on Erlang’s conventions for error handling.

That is, despite the lack of static types, Erlang and Elixir model success and failure in a very similar way to Elm. Given it can not be encoded in a data type or enforced by the compiler it is addressed as a rule or convention in the official Ericsson Erlang Programming Rules and Conventions guide in section 6.3 Use tagged return values.

So, in the absence of an Abstract Data Type/Tagged Union/Union Type as in Elm, it uses tuples and atoms to tag the return value in a very similar way.

Remember, in Elm the Result looked like this in error and success cases respectively:

Err "NaN"
Ok 1

In Elixir it would look like this:

{:error, "NaN"}
{:ok, 1}

It is a simple two-tuple with the first element, an atom, as the ‘tag’, and the second element, a value, as the return value of the operation. We can generalise:

{:ok, result}     # Ok value
{:error, reason}  # Err error

This is actually how Ecto.Changeset.apply_action/2 works. If the action can be successfully be applied then it returns {:ok, data} otherwise it will return {:error, changeset}.

If you look at the Elixir standard library then you see examples of this permeating the majority of the provided data-types. However, although the terminology of :error is used everywhere, the shape of the return data is sometimes different and actually resembles the Elm Maybe type. Take an access operation on a Keyword list, Keyword.fetch/2, it exhibits the following behaviour:

> Keyword.fetch([a: 1], :a)
{:ok, 1}
> Keyword.fetch([a: 1], :b)

The :error here is not tagging anything, it stands on its own, and thus performs the functional equivalent of Nothing in Elm. That is:

Just 1    -- {:ok, 1}
Nothing   -- :error

And so we can see that although it doesn’t have exactly the same tools available to it as Elm, Elixir can adopt some of the error mechanisms of this explicit style.1 We can make errors modes explicit and cater to these within our programs.

What Elixir doesn’t give you is a straight-jacket like Elm, and you are left very much to your own devices. Error handling can be done with nil, with these tagged options or results, or with exception handling. In Elm you are forced down one path. In both languages you are able to elevate error handling to be an explicit and managed part of your program flow and execution.

Thursday 4th February 2021.

  1. Implementing Maybe and Result in Ruby is also perfectly possible and an interesting experiment. You will soon discover that it feels very much like working against the grain compared with Elm and Elixir.
  2. Some say that in Elixir you have the best of all possible worlds, but others will be quick to remind you that you also have the worst. Choose your potion/poison.