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

Yeah I am just using them as a placeholder until I get later code complete that will use them. I can update them later with the actual type they will need to be and get compilation failures everywhere that currently use it, so it works well for my style. :slight_smile:

Edit: Oh, and you are not injecting types there, rather that only works with Records (not unions or anything else), and it makes a ‘new’ record type of your extended type. :slight_smile:

In Scala

  • Option is to deal with null values (contains some type T or null)
  • Either can be used to deal with errors, basically can be type A or B
  • Try is special type for error (contains Result or Error)

The Neophyte’s Guide to Scala Part 6: Error Handling With Try

The Neophyte’s Guide to Scala Part 5: The Option Type

Functors, Applicatives, And Monads In Pictures

1 Like

maybe http://www.purescript.org/ ? :slight_smile:

To be honest, currently my favorite is idris, but learning idris alone is a task I will postpone until I finished my thesis (or maybe learn it as my thesis’ topic) :slight_smile:

Idris does have several backends, one of them is JS. But currently it emits JS targeted at node-js and not able beeing run in a browser. Making this possible, as well as implementing an API that gives access to the DOM is a mid to high priority task.

1 Like

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

FYI for those interested here are some more related projects:

2 Likes

Ooo, that is interesting. I like how the case is part of the main module for the union. I may have to look closely at this. :slight_smile:

I am currently in the process of cleaning up this library.

Funland’s version 0.8 will have many fixes and better documentation.
The main reason why I do not feel ready yet to release a 1.0 is that the FunLandic namespace, which includes a set of common Algebraic Data Type implementations, is still part of the library.

I am not entirely sure what makes most sense for extracting the two. Probably FunLandic will be moved to its own library, but this will make it harder to test FunLand itself, and maybe also harder to understand for newcomers, as there are then no clear examples of structures implementing the behaviours inside FunLand itself.

What to do, what to do…

Another issue, which I have solved for now, but maybe there is a better way, is that certain functions like traverse work wonderful in a statically-typed language where the function used in the implementation depends on the return type. But in Elixir, we do not have this information, so traverse will take as extra argument the module of the return type you want to build (or a datatype indicating that module):

      iex> FunLand.Traversable.traverse([1, 2, 3], FunLandic.Maybe, fn x -> FunLandic.Maybe.just(x) end)
      FunLandic.Maybe.just([1, 2, 3])
      iex> FunLand.Traversable.traverse([1, 2, 3], [], fn x -> [x,x] end)
      [[1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3],
      [1, 2, 3]]

I think this is as idiomatic as it is going to get…

Help is greatly appreciated :slight_smile: .

By the way: FunLand is treating success tuples as a first-class type:

The simple mapping example:

iex> FunLand.map({:ok, 1}, fn x -> x * 10 end)
{:ok, 10}
iex> FunLand.map({:error, :failure}, fn x -> x * 10 end)
{:error, :failure}
iex> {:ok, 3} |> FunLand.map(Currying.curry(&+/2)) |> FunLand.apply_with({:ok, 10})
{:ok, 13}

The full-blown monad example:

      iex> require FunLand.Monad
      iex> FunLand.Monad.monadic({:ok, nil}) do
      iex>   x <- {:ok, 10}
      iex>   y <- {:ok, 20}
      iex>   new(x * y)
      iex> end
      {:ok, 200}
      iex> require FunLand.Monad
      iex> FunLand.Monad.monadic({:ok, nil}) do
      iex>   x <- {:ok, 10}
      iex>   y <- {:error, :something_went_wrong}
      iex>   new(x * y)
      iex> end
      {:error, :something_went_wrong}

Hah, very cool! I’m having thoughts of adding it to MLElixir to see how it would look. ^.^

1 Like

I find this post about “Sum type” (union type) to be very helpful!

1 Like

Nice!

Structs are product types. I wonder in what way we could simulate Sum types in Elixir. What I can think of so far is to ‘emulate’ them by having a struct where one of the keys is only ever one of a small subset of allowed atoms.

1 Like

In regards to the Maybe type, I am not amazingly well-versed in functional programming or Elixir but I was under the impression that these were unnecessary in Elixir because of function return values like :error and {:ok, value}. Does anyone know of any use cases in which these conventions would be inferior to a Maybe type (or Option type)?

@jswny {:ok, value} | :error, als known as the ok-Tuple or Success Tuple, is exactly a Maybe type.

However, because it is a special kind of tuple, and not a struct, it is not possible to use it in protocol dispatching. Furthermore, (and in part because of that,) Elixir is missing many of the nice functions/functionality that could be written for the Maybe type.

This is exactly what FunLand provides: (Besides other behaviours and implementations) it provides the possibility to map functions over the Success Tuple, combine the results of two Success Tuples, put arbitrary values inside a new Success Tuple, etc.

2 Likes

Which I still find odd. Protocols just compile to multiple function heads yes? That means it should be trivial to match on structure…