defmodule Algae.Either do
@moduledoc ~S"""
Represent branching conditions. These could be different return types,
error vs nominal value, and so on.
## Examples
iex> require Integer
...>
...> even_odd = fn(value) ->
...> if Integer.is_even(value) do
...> Algae.Either.Right.new(value)
...> else
...> Algae.Either.Left.new(value)
...> end
...> end
...>
...> even_odd.(10)
%Algae.Either.Right{right: 10}
...> even_odd.(11)
%Algae.Either.Left{left: 11}
"""
import Algae
defsum do
defdata Left :: any()
defdata Right :: any()
end
end
alias Algae.Either.{Left, Right}
import TypeClass
use Witchcraft
#############
# Generator #
#############
defimpl TypeClass.Property.Generator, for: Algae.Either.Left do
def generate(_) do
[1, 1.1, "", []]
|> Enum.random()
|> TypeClass.Property.Generator.generate()
|> Algae.Either.Left.new()
end
end
defimpl TypeClass.Property.Generator, for: Algae.Either.Right do
def generate(_) do
[1, 1.1, "", []]
|> Enum.random()
|> TypeClass.Property.Generator.generate()
|> Algae.Either.Right.new()
end
end
##########
# Setoid #
##########
definst Witchcraft.Setoid, for: Algae.Either.Left do
def equivalent?(_, %Right{}), do: false
def equivalent?(%Left{left: a}, %Left{left: b}), do: Witchcraft.Setoid.equivalent?(a, b)
end
definst Witchcraft.Setoid, for: Algae.Either.Right do
def equivalent?(_, %Left{}), do: false
def equivalent?(%Right{right: a}, %Right{right: b}), do: Witchcraft.Setoid.equivalent?(a, b)
end
#######
# Ord #
#######
definst Witchcraft.Ord, for: Algae.Either.Left do
custom_generator(_) do
1
|> TypeClass.Property.Generator.generate()
|> Left.new()
end
def compare(_, %Algae.Either.Right{}), do: :lesser
def compare(%Left{left: a}, %Left{left: b}), do: Witchcraft.Ord.compare(a, b)
end
definst Witchcraft.Ord, for: Algae.Either.Right do
custom_generator(_) do
1
|> TypeClass.Property.Generator.generate()
|> Right.new()
end
def compare(_, %Left{}), do: :greater
def compare(%Right{right: a}, %Right{right: b}), do: Witchcraft.Ord.compare(a, b)
end
#############
# Semigroup #
#############
definst Witchcraft.Semigroup, for: Algae.Either.Left do
custom_generator(_) do
1
|> TypeClass.Property.Generator.generate()
|> Left.new()
end
def append(left, %Right{}), do: left
def append(%Left{left: a}, %Left{left: b}), do: %Left{left: a <> b}
end
definst Witchcraft.Semigroup, for: Algae.Either.Right do
custom_generator(_) do
1
|> TypeClass.Property.Generator.generate()
|> Algae.Either.Right.new()
end
def append(_, left = %Left{}), do: left
def append(%Right{right: a}, %Right{right: b}), do: %Right{right: a <> b}
end
##########
# Monoid #
##########
definst Witchcraft.Monoid, for: Algae.Either.Left do
def empty(%Left{left: a}), do: %Right{right: Witchcraft.Monoid.empty(a)}
end
definst Witchcraft.Monoid, for: Algae.Either.Right do
def empty(%Right{right: a}), do: %Right{right: Witchcraft.Monoid.empty(a)}
end
# ###########
# # Functor #
# ###########
definst Witchcraft.Functor, for: Algae.Either.Left do
def map(left, _), do: left
end
definst Witchcraft.Functor, for: Algae.Either.Right do
def map(%Right{right: data}, fun), do: data |> fun.() |> Right.new()
end
# ############
# # Foldable #
# ############
definst Witchcraft.Foldable, for: Algae.Either.Left do
def right_fold(_, seed, _), do: seed
end
definst Witchcraft.Foldable, for: Algae.Either.Right do
def right_fold(%Right{right: inner}, seed, fun), do: fun.(inner, seed)
end
# ###############
# # Traversable #
# ###############
definst Witchcraft.Traversable, for: Algae.Either.Left do
@force_type_instance true
def traverse(left = %Left{left: value}, link) do
value
|> link.()
|> of(left)
end
end
definst Witchcraft.Traversable, for: Algae.Either.Right do
def traverse(%Right{right: value}, link), do: map(link.(value), &Right.new/1)
end
# #########
# # Apply #
# #########
definst Witchcraft.Apply, for: Algae.Either.Left do
def convey(left, _), do: left
end
definst Witchcraft.Apply, for: Algae.Either.Right do
def convey(_, left = %Left{}), do: left
def convey(data, %Right{right: fun}), do: map(data, fun)
end
###############
# Applicative #
###############
definst Witchcraft.Applicative, for: Algae.Either.Left do
@force_type_instance true
def of(_, data), do: Right.new(data)
end
definst Witchcraft.Applicative, for: Algae.Either.Right do
def of(_, data), do: Right.new(data)
end
# #########
# # Chain #
# #########
definst Witchcraft.Chain, for: Algae.Either.Left do
def chain(left, _), do: left
end
definst Witchcraft.Chain, for: Algae.Either.Right do
def chain(%Right{right: data}, link), do: link.(data)
end
# #########
# # Monad #
# #########
definst Witchcraft.Monad, for: Algae.Either.Left
definst Witchcraft.Monad, for: Algae.Either.Right
# ##########
# # Extend #
# ##########
definst Witchcraft.Extend, for: Algae.Either.Left do
def nest(_), do: Left.new()
end
definst Witchcraft.Extend, for: Algae.Either.Right do
def nest(inner), do: Right.new(inner)
end