There was a proposition for that, see elixir-lang/elixir#925. Based on that I’ve implemented this library disc_union. It uses a little different naming, but basically works just as you explain it. The example below is an implementation of tennis kata:
defmodule Player do
use DiscUnion
defunion A | B
end
defmodule PlayerPoints do
use DiscUnion
defunion Love | Fifteen | Thirty | Forty
end
defmodule Score do
use DiscUnion
require PlayerPoints
defunion Points in PlayerPoints * PlayerPoints
| Advantage in Player
| Deuce
| Game in Player
end
defmodule Tennis do
require Player
require PlayerPoints
require Score
def score_point(%Score{}=score, %Player{}=point_player) do
IO.puts "Point for player #{inspect point_player} @ #{inspect score}"
Score.case score do
Advantage in ^point_player -> Score.game point_player
Advantage in _ -> Score.deuce
Deuce -> Score.advantage point_player
Points in PlayerPoints.forty, _ when point_player==Player.a -> Score.game Player.a
Points in _, PlayerPoints.forty when point_player==Player.b -> Score.game Player.b
Points in a, b when point_player==Player.a -> Score.points(next_point_score(a), b) |> normalize_score
Points in a, b when point_player==Player.b -> Score.points(a, next_point_score(b)) |> normalize_score
Game in _ -> IO.puts "Game is over #{inspect score}"
end
end
defp next_point_score(%PlayerPoints{}=point) do
PlayerPoints.case point do
Love -> PlayerPoints.fifteen
Fifteen -> PlayerPoints.thirty
Thirty -> PlayerPoints.forty
Forty -> raise "WAT?"
end
end
defp normalize_score(%Score{}=score) do
Score.case score, allow_underscore: true do
Points in PlayerPoints.forty, PlayerPoints.forty -> Score.deuce
_ -> score
end
end
end
I admit DSL syntax is inspired by OCaml.
It even generates compile-time warnings when one of defined cases is not covered in case body.






















