ExUnion - Tagged Unions for Elixir. Just that

ExUnion is meant to be a lightweight, elixir-y implementation of tagged unions (also called variant, discriminated union, sum type, etc.).

While conventionally Elixir tends to promote using tuples to model tagged unions - the {:ok, ...} | {:error, ...} pattern being a good example of that - this approach arguably lacks expressiveness, especially when modeling non-trivial unions. An alternative is to employ structs to model the individual cases of a tagged union, which works nicely but has the disadvantage of requiring significant boilerplate code.

ExUnion attempts to bridge this gap by generating the necessary boilerplate (and a bit more) through a concise albeit opinionated DSL. Below the following links you can see an example for an idea on how you might use ExUnion.

defmodule Maybe do
  import ExUnion

  defunion some(value) | none

iex> Maybe.none()

iex> Maybe.some("string!")
%Maybe.Some{value: "string!"}

# Requiring is necessary since `is_maybe` is a guard (defguard)
iex> require Maybe
iex> Maybe.is_maybe("What's the meaning of life, the universe, and everything?")
iex> Maybe.is_maybe(42)
iex> Maybe.is_maybe(Maybe.none())

For more examples take a look at the project’s README.


Pretty nice, I do like it.

Is the following usage considered asserting on information that’s an implementation detail? Or this is exactly how your library should be used when it comes to pattern matching?

def do_stuff_with(%Maybe.None{}), do: :dont_do_anything
def do_stuff_with(%Maybe.Some{...}), do: :do_the_thing

That’s exactly how it’s supposed to be used. It’s also why the generated code contains type specs to more readily reveal the underlying structure.

Guess I could be more explicit in the docs about that.

1 Like

The docs are good, I am simply making sure because e.g. Ecto still has some structs whose fields are well-known but it’s still not recommended to rely on them always being there. Hence my question.

Thanks for clarifying. :+1:

1 Like

v0.1.1 - Resolve dialyzer warnings


  • #2: Resolve dialyzer warnings

Link to GitHub release

Thanks for the splendid library! Now the choice types can be expressed even shorter than in F# (comparing to the examples from Domain Modeling Made Functional book by Scott Wlaschin).

1 Like

v0.1.2 - Include .formatter.exs in the published package


  • Included .formatter.exs in the published package to enable import_deps: [:ex_union]

Link to GitHub release

This seems like a nice library.

One thing I’m wondering is how to do custom validations. For example in your color example:

defmodule Color do
  import ExUnion

  defunion hex(string :: String.t)
           | rgb(red :: 0..255, green :: 0..255, blue :: 0..255)
           | rgba(red :: 0..255, green :: 0..255, blue :: 0..255, alpha :: float)
           | hsl(hue :: 0..360, saturation :: float, lightness :: float)
           | hsla(hue :: 0..360, saturation :: float, lightness :: float, alpha :: float)

Let’s say I wanted to validate that the hex value passed in has the correct format.

So is there a way to create a custom constructor for the Color.hex?

Not yet, it’s one of the things I’d like to explore, in addition to protocol derivation.