lib/algae/either.ex

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