Qqwy

Qqwy

TypeCheck Core Team

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

Because of popular demand, and because I’ll probably be busy the next couple of days, so it would need to wait a lot longer if I didn’t publish it now, here it is:

FunLand: This is a package that adds a couple behavours to your Elixir application, which you can use to define Algebraic Data Types.

What exactly are Algebraic Data Types?

They are basically containers for simpler types, in all kinds and shapes.

Some common examples are:

  • Lists (as use already every day in Elixir)
  • Tuples
  • Trees
  • Maybe, which either contains a single value or nothing. This allows for propagation of failures in more complex operations.
  • Writers, which allow you to keep track of something (such as a log) in the background while passing it through multiple operations that work on simple values.

Why are Algebraic Data Types useful?

Algebraic Data Types are useful in the same way that using loops is useful: They let you re-use a set of operations you already had on a much larger set of inputs/problems.

For instance, Mappable.map lets you re-use any function that works on a single simple type, to tranform the contents of a collection of things of that type:

Mappable.map(input, fn x -> x*2 end) would transform the list [1,2,3] into the list [2,4,6], the tuple {3,1,4} into {6,2,8}, Maybe.just(6) into Maybe.just(12), etc.


This pre-release of FunLand is mainly because I would like some feedback, and to find out if the design choices I have made so far are sound. To implement Abstract Data Types turned out to be a larger endeavour than I had expected. I hope that FunLand will be able to explain to newcomers how ADTs work and why they are useful, and make it easy for people to define their own.

also, Pull Requests are very welcome! :stuck_out_tongue_winking_eye:

Sincerely,

~Wiebe-Marten/Qqwy

Most Liked

X4lldux

X4lldux

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.

NobbZ

NobbZ

You describe ADTs as Containers for some other types, thats not the only truth. ADTs are much more complex.

When introducing ADTs I’d do roughly the following order:

  1. Simple enumerations as in data Bool = False | True or data Fruits = Apple | Peach | Orange.
  2. Record/Struct Like as in data Person = Person String Date.
  3. Replacement for tagged unions as in data NPC = Monster String Int | Merchant String [(Item, Int)].
  4. Introduce type-variables as in data Maybe a = Just a | Nothing and explain that this is an extension to the former ones.

ADT itself are not necessary to define classes (Haskell term) or interfaces (Idris term) since they are very similar to what the OO-World does call an interface ever since.

As you can see, ADT is more or less an abstraction of what the C world does know as 3 separate concepts: enum, struct, and (tagged) union.

sotojuan

sotojuan

FYI for those interested here are some more related projects:

Qqwy

Qqwy

TypeCheck Core Team

@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.

NobbZ

NobbZ

I can’t speak for Scala, but for Rust I can tell that Result<A, B> is similar to Haskells Either a b, while Rusts Option<A> is similar to Haskells Maybe a.

Also even if often done like this, I wouldn’t say that Either is to signal value or error, but signals the possible outcome of a computation. Consider \n -> if n >= 0 then Right n else Left (-n) (Haskell). This example is somewhat constructed but shows that Either is not always about an error.

Thats the reason why there is (in Rust at least) often an additional Error<A>/Error a which does alias to some according Either String a.

Also, even if it is common and idiomatic to use Right for success, this is not necessarily true for every language! Idris does use Either the other way round and uses Left for success (its not common though to use Maybe or Either though).

Where Next?

Popular in Libraries Top

scohen
Lexical Lexical is a next-generation language server for the Elixir programming language. Features Context aware code completion As-you...
New
mathieuprog
Hello :wave: Allow me to introduce you to Tz, an alternative time zone database support to Tzdata. Why another library? First and fore...
New
KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 35421 110
New
cjen07
parameterized pipe in elixir: |n&gt; edit: negative index in |n&gt; and mixed usage with |&gt; are supported example: use ParamP...
New
mbuhot
Leverage Open Api 3.0 (Swagger) to document, test, validate and explore your Plug and Phoenix APIs. Generate and serve a JSON Open API ...
New
woylie
Flop is an Elixir library that applies filtering, ordering and pagination parameters to your Ecto queries. offset-based pagination with...
New
zorbash
I created Kitto a framework for dashboards inspired by Dashing. [demo] The distributed characteristics of Elixir and the low memory foo...
New
engineeringdept
I’ve just released the first version of Snap, an Elasticsearch client. It borrows ideas about application structure and process managemen...
New
tmbb
I’ve decided to create this topic to discuss optimization possibilities for something like Phoenix LiveView. I’ve created this topic unde...
New
versilov
Could not wait for the missing Elixir ML libraries to appear, so, I wrote one myself, taking https://github.com/sdwolfz/exlearn as a foun...
New

Other popular topics Top

bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
albydarned
Hello all! I am typing this post from my new MacBook Pro with the M1 chip. I’m loving it so far, and will probably use it as my daily dr...
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
sergio
I couldn’t find any guides that worked well with Phoenix 1.6.0 and esbuild. I hope this helps people test the waters and eases you into t...
New
chrismccord
This release brings a number of exciting features, including integration with the new Phoenix LiveDashboard and Phoenix LiveView. There h...
New
freewebwithme
Using vs code and installed ElixirLS: support and debugger. And I got an error popped up on start up says Failed to run ‘elixir’ comma...
New
ovidiubadita
Hey all, I discovered Elixir and I love it. I always wanted to learn a functional programming and I intended to go for Haskell, but afte...
New
WestKeys
Currently suffering from paralysis by [HTTP client] analysis. This is rather unusual in Elixirland as there tends to be consensus on the ...
New
beno
I will often find my self writing things similar to: case some_value do nil -&gt; something() "" -&gt; something() _ -&gt; someth...
New
9mm
I am constructing a JSON object (map) and I need to conditionally set a field. I’m trying to write proper elixir-way code… and I’m at a l...
New

Sub Categories:

We're in Beta

About us Mission Statement