FunLand: Algebraic("Container") Data Types for Elixir - Prerelease

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.

3 Likes