Packages

FunLand adds Behaviours to define Algebraic Data Types ('Container' data types) to Elixir, such as Functors, Monoids and Monads.

Current section

Files

Jump to
fun_land lib fun_land.ex
Raw

lib/fun_land.ex

defmodule FunLand do
@moduledoc """
FunLand defines many different Algebraic Data Types.
An Algebraic Data Type is nothing more, than a 'container' for some other data type.
Lists are ADTs. And so are Maps. And Sets. And Tuples. And many other things.
Algebraic Data Types have no intrinsic value of their own. They get value, once you fill them with something,
and have operations you can perform on their contents.
There are many similarities in the way the different ADTs work. This allows us to define behaviours which
generalize to all ADTs. Any of your custom data types that you can implement one or multiple of these behaviours for,
is an ADT, and will receive the benefits that the implemented ADTs give.
Another nice thing about this generalization, is that there is no 'learning a new API' necessary when
switching to one thing-that-is-an-ADT to the next.
To easily use FunLand in your code, call `use FunLand`, which will alias for you:
- `Mappable` -> A structure is Mappable if there is a way to map a function over it: transforming the contents but keeping the structure.
- `Appliable` -> A structure is Applibable if it is Mappable and, given two of them where the first contains a partially-applied function, you can apply them together.
- `Applicative` -> A structure is Applicative if it is Appliable and you can create a new one by wrapping any value.
- `Chainable` -> A structure is Chainable if it is Appliable and you can chain multiple operations after each other, resulting in just a single structure.
- `Monad` -> A structure is a Monad if it is both Applicative and Chainable.
- `Semicombinable` -> A structure is Semicombinable if there is a way to combine two structures into one.
- `Combinable` -> A structure is Combinable if it is Semicombinable and there is a clearly defined 'neutral' element.
- `CombinableMonad` -> A structure is a CombinableMonad if it is both Combinable and a Monad.
- `Reducable` -> A structure is reducable if you can fold/reduce it to a single value, when giving a Combinable or function+default.
- `Traversable` -> A structure is Traversable if it is Reducable and there is a way to flip the ???
It will also import the following operators:
- `~>` Shorthand for `Mappable.map/2`
- `<~>` Shorthand for `Appliable.apply_with/2`
- `~>>` Shorthand for `Chainable.chain/2`
- `<>` Shorthand for `Combinable.combine/2`. This operator still works the same for binaries, but will now also work for any other Chainable.
If you want, you can also call `use FunLand, operators: false` to not import these operators.
"""
# Elixir doesn't let you _really_ define algebraic data types, so we're creating a 'one type fits all' type.
@type adt :: [any] | {} | map | struct
defmacro __using__(opts) do
# Only import operators if wanted.
import_code =
if opts[:operators] == false do
quote do
import Kernel
import FunLand, except: [<>: 2, ~>: 2, <~>: 2, ~>>: 2]
end
else
quote do
import Kernel, except: [<>: 2]
import FunLand
end
end
quote do
unquote(import_code)
alias FunLand.{
Mappable,
Appliable,
Applicative,
Chainable,
Monad,
Semicombinable,
Combinable,
Reducable,
Traversable,
CombinableMonad,
}
end
end
@doc """
Infix version of `FunLand.Appliable.map/2`
"""
def a ~> b do
FunLand.Mappable.map(a, b)
end
@doc """
Infix version of `FunLand.Appliable.apply_with/2`
"""
def a <~> b do
FunLand.Appliable.apply_with(a, b)
end
@doc """
Infix version of `FunLand.Chainable.chain/2`
"""
def a ~>> b do
FunLand.Chainable.chain(a, b)
end
# This operator is made more general. It still works for binary combining, as binaries are indeed Combinable.
@doc """
Infix version of `FunLand.Combinable.combine/2`.
Note that binary strings are Combinable, so "foo" <> "bar" still works.
`<>/2` can still be used in pattern-matches and guard clauses, but it will fall back to the
behavior of `Kernel.<>/2`, which means that it will only work with binary strings.
"""
defmacro left <> right do
in_module? = (__CALLER__.context == nil)
if in_module? do
quote do
FunLand.Combinable.combine(unquote(left), unquote(right))
end
else
quote do
Kernel.<>(unquote(left), unquote(right))
end
end
end
defmodule Helper do
@moduledoc """
FunLand Helper functions for some common Algorithmic Data Type implementations.
"""
def id(x), do: x
def const(x, _y), do: x
def const_reverse(_x, y), do: y
end
defmodule Builtin do
def __builtin__ do
[is_tuple: FunLand.Builtin.Tuple,
is_atom: FunLand.Builtin.Atom,
is_list: FunLand.Builtin.List,
is_map: FunLand.Builtin.Map,
is_bitstring: FunLand.Builtin.BitString,
is_integer: FunLand.Builtin.Integer,
is_float: FunLand.Builtin.Float,
is_function: FunLand.Builtin.Function,
is_pid: FunLand.Builtin.PID,
is_port: FunLand.Builtin.Port,
is_reference: FunLand.Builtin.Reference]
end
def __stdlib__ do
stdlib_modules = ~w{List Tuple Atom Map BitString Integer Float Function PID Port Reference}
for module <- stdlib_modules do
{:"Elixir.#{module}", :"Elixir.FunLand.Builtin.#{module}"}
end
end
end
end