lib/noether/maybe.ex

defmodule Noether.Maybe do
  @moduledoc nil

  alias Noether.Either
  @type fun1 :: (any() -> any())

  @doc """
  Given a value and a function, the function is applied only if the value is different from `nil`. `nil` is returned otherwise.

  ## Examples

      iex> map(nil, &Kernel.abs/1)
      nil

      iex> map(-1, &Kernel.abs/1)
      1
  """
  @spec map(any(), fun1()) :: any()
  def map(nil, _), do: nil
  def map(a, f) when is_function(f, 1), do: f.(a)

  @doc """
  Given a value and a default, `{:ok, value}` is returned only if the value is different from `nil`. `{:error, default}` is returned otherwise.

  ## Examples

      iex> required(nil, :hello)
      {:error, :hello}

      iex> required(1, :hello)
      {:ok, 1}
  """
  @spec required(any(), any()) :: Either.either()
  def required(nil, default), do: {:error, default}
  def required(a, _), do: {:ok, a}

  @doc """
  Given a value, a function, and a default, it applies the function on the value if the latter is different from `nil`. It returns the default otherwise.

  ## Examples

      iex> maybe(-1, &Kernel.abs/1, :hello)
      1

      iex> maybe(nil, &Kernel.abs/1, :hello)
      :hello
  """
  @spec maybe(any(), fun1(), any()) :: any()
  def maybe(nil, _, default), do: default
  def maybe(a, f, _) when is_function(f, 1), do: f.(a)

  @doc """
  Given a list of values, the function is mapped only on the elements different from `nil`. `nil` values will be discarded. A list of the results is returned.

  ## Examples
      iex> cat_maybe([1], &(&1 + 1))
      [2]

      iex> cat_maybe([1, nil, 3], &(&1 + 1))
      [2, 4]
  """
  @spec cat_maybe([any()], fun1()) :: [any()]
  def cat_maybe(a, f) when is_function(f, 1) do
    a
    |> Enum.reduce([], fn
      nil, acc -> acc
      b, acc -> [f.(b) | acc]
    end)
    |> Enum.reverse()
  end

  @doc """
  Given a value and two functions, it applies the first one and returns the result if it's different from nil. Otherwise the second function is applied.

  ## Examples

      iex> choose(0, fn a -> a + 1 end, fn b -> b + 2 end)
      1

      iex> choose(0, fn _ -> nil end, fn b -> b + 2 end)
      2

      iex> choose(0, fn _ -> nil end, fn _ -> nil end)
      nil
  """
  @spec choose(any(), fun1(), fun1()) :: any()
  def choose(a, f, g) when is_function(f, 1) and is_function(g, 1) do
    b = f.(a)

    if is_nil(b) do
      g.(a)
    else
      b
    end
  end
end