lib/warm_fuzzy_thing.ex

defmodule WarmFuzzyThing do
  @moduledoc """
  Provides a way of working with already known Elixir constructs through monads.

  `WarmFuzzyThing.Maybe` represents the Union `nil | {:ok, value} when value: any()`.

  `WarmFuzzyThing.Either` represents the Union `{:error, reason} | {:ok, value} when reason: any(), value: any()`.

  _To understand more about their general idea and implementation details, head over to their respective documentation._
  """

  alias WarmFuzzyThing.Either
  alias WarmFuzzyThing.Maybe

  @type t(a) :: Either.t(any(), a) | Maybe.t(a)

  @callback pure(a) :: WarmFuzzyThing.t(a) when a: any()
  @callback fmap(WarmFuzzyThing.t(a), (a -> b)) :: WarmFuzzyThing.t(b) when a: any(), b: any()
  @callback bind(WarmFuzzyThing.t(a), (a -> WarmFuzzyThing.t(b))) :: WarmFuzzyThing.t(b) when a: any(), b: any()
  @callback fold(WarmFuzzyThing.t(a), default :: b, (a -> b)) :: b when a: any(), b: any()
  @callback sequence([WarmFuzzyThing.t(a)]) :: WarmFuzzyThing.t([a]) when a: any()

  @doc """
  Functions that return the value itself.

  ## Example
      iex> WarmFuzzyThing.id(10)
      10
      iex> WarmFuzzyThing.id({:ok, 10})
      {:ok, 10}
      iex> WarmFuzzyThing.id({:error, :not_found})
      {:error, :not_found}
      iex> WarmFuzzyThing.id(nil)
      nil
  """
  @spec id(value) :: value when value: any()
  def id(value), do: value

  @doc """
  Converts a `WarmFuzzyThing.Maybe` to `WarmFuzzyThing.Either`

  ## Example
      iex> WarmFuzzyThing.maybe_to_either({:ok, 1}, :not_found)
      {:ok, 1}
      iex> WarmFuzzyThing.maybe_to_either(nil, :not_found)
      {:error, :not_found}
  """
  @spec maybe_to_either(Maybe.t(value), reason) :: Either.t(reason, value)
        when value: any(),
             reason: any()
  def maybe_to_either(maybe, reason)

  def maybe_to_either(nil, reason), do: {:error, reason}
  def maybe_to_either({:ok, value}, _), do: {:ok, value}

  @doc """
  Converts a `WarmFuzzyThing.Either` to `WarmFuzzyThing.Maybe`

  ## Example
      iex> WarmFuzzyThing.either_to_maybe({:ok, 1})
      {:ok, 1}
      iex> WarmFuzzyThing.either_to_maybe({:error, :not_found})
      nil
  """
  @spec either_to_maybe(Either.t(reason, value)) :: Maybe.t(value)
        when value: any(),
             reason: any()
  def either_to_maybe(either)
  def either_to_maybe({:error, _reason}), do: nil
  def either_to_maybe({:ok, value}), do: {:ok, value}
end