defprotocol Funx.Monoid do
  @moduledoc """
  A protocol defining the Monoid algebraic structure, which consists of
  an identity element and an associative binary operation for combining values.

  This protocol provides four key functions:

  - `empty/1`: Returns the identity element for the given monoid.
  - `append/2`: Combines two monoid structs.
  - `wrap/2`: Wraps a value into the monoid struct.
  - `unwrap/1`: Extracts the underlying value from the monoid struct.
  """

  @doc """
  Returns the identity element for the given monoid struct.

  The identity element is a special value that satisfies the property:

      append(empty(monoid_struct), x) == x
      append(x, empty(monoid_struct)) == x

  ## Examples

      iex> Funx.Monoid.empty(%Funx.Monoid.Sum{})
      %Funx.Monoid.Sum{value: 0}
  """
  @spec empty(t()) :: t()
  def empty(monoid_struct)

  @doc """
  Combines two monoid structs.

  The operation must satisfy associativity:

      append(append(a, b), c) == append(a, append(b, c))

  ## Examples

      iex> Funx.Monoid.append(%Funx.Monoid.Sum{value: 1}, %Funx.Monoid.Sum{value: 2})
      %Funx.Monoid.Sum{value: 3}
  """
  @spec append(t(), t()) :: t()
  def append(monoid_struct_a, monoid_struct_b)

  @doc """
  Wraps a value into the given monoid struct.

  ## Examples

      iex> Funx.Monoid.wrap(%Funx.Monoid.Sum{}, 10)
      %Funx.Monoid.Sum{value: 10}
  """
  @spec wrap(t(), any()) :: t()
  def wrap(monoid_struct, value)

  @doc """
  Extracts the underlying value from the monoid struct.

  ## Examples

      iex> Funx.Monoid.unwrap(%Funx.Monoid.Sum{value: 10})
      10
  """
  @spec unwrap(t()) :: any()
  def unwrap(monoid_struct)
end
