defprotocol Funx.Eq do @moduledoc """ The `Funx.Eq` protocol defines an equality function, `eq?/2`, for comparing two values, and its complement, `not_eq?/2`, for checking inequality. Types that implement this protocol can define custom equality logic, allowing for domain-specific comparisons. ## Fallback The protocol uses `@fallback_to_any true`, meaning that if a specific type does not implement `Funx.Eq`, it falls back to the default implementation for `Any`, which uses Elixir's built-in equality operator (`==`). ## Examples With a custom implementation for a `Funx.Monad.Maybe` type: iex> Funx.Eq.eq?(Funx.Monad.Maybe.just(3), Funx.Monad.Maybe.just(3)) true iex> Funx.Eq.eq?(Funx.Monad.Maybe.just(3), Funx.Monad.Maybe.just(5)) false iex> Funx.Eq.eq?(Funx.Monad.Maybe.nothing(), Funx.Monad.Maybe.nothing()) true iex> Funx.Eq.eq?(Funx.Monad.Maybe.nothing(), Funx.Monad.Maybe.just(5)) false Checking inequality with `not_eq?/2`: iex> Funx.Eq.not_eq?(Funx.Monad.Maybe.just(3), Funx.Monad.Maybe.just(3)) false iex> Funx.Eq.not_eq?(Funx.Monad.Maybe.just(3), Funx.Monad.Maybe.just(5)) true iex> Funx.Eq.not_eq?(Funx.Monad.Maybe.nothing(), Funx.Monad.Maybe.nothing()) false iex> Funx.Eq.not_eq?(Funx.Monad.Maybe.nothing(), Funx.Monad.Maybe.just(5)) true """ @fallback_to_any true @doc """ Returns `true` if `a` is equal to `b`, otherwise returns `false`. ## Examples iex> Funx.Eq.eq?(1, 1) true iex> Funx.Eq.eq?(1, 2) false """ def eq?(a, b) @doc """ Returns `true` if `a` is not equal to `b`, otherwise returns `false`. ## Examples iex> Funx.Eq.not_eq?(1, 1) false iex> Funx.Eq.not_eq?(1, 2) true """ def not_eq?(a, b) end defimpl Funx.Eq, for: Any do @spec eq?(any(), any()) :: boolean() def eq?(a, b), do: a == b @spec not_eq?(any(), any()) :: boolean() def not_eq?(a, b), do: a != b end defimpl Funx.Eq, for: DateTime do @spec eq?(DateTime.t(), DateTime.t()) :: boolean() def eq?(%DateTime{} = a, %DateTime{} = b), do: DateTime.compare(a, b) == :eq @spec not_eq?(DateTime.t(), DateTime.t()) :: boolean() def not_eq?(%DateTime{} = a, %DateTime{} = b), do: DateTime.compare(a, b) != :eq end defimpl Funx.Eq, for: Date do @spec eq?(Date.t(), Date.t()) :: boolean() def eq?(%Date{} = a, %Date{} = b), do: Date.compare(a, b) == :eq @spec not_eq?(Date.t(), Date.t()) :: boolean() def not_eq?(%Date{} = a, %Date{} = b), do: Date.compare(a, b) != :eq end defimpl Funx.Eq, for: Time do @spec eq?(Time.t(), Time.t()) :: boolean() def eq?(%Time{} = a, %Time{} = b), do: Time.compare(a, b) == :eq @spec not_eq?(Time.t(), Time.t()) :: boolean() def not_eq?(%Time{} = a, %Time{} = b), do: Time.compare(a, b) != :eq end defimpl Funx.Eq, for: NaiveDateTime do @spec eq?(NaiveDateTime.t(), NaiveDateTime.t()) :: boolean() def eq?(%NaiveDateTime{} = a, %NaiveDateTime{} = b), do: NaiveDateTime.compare(a, b) == :eq @spec not_eq?(NaiveDateTime.t(), NaiveDateTime.t()) :: boolean() def not_eq?(%NaiveDateTime{} = a, %NaiveDateTime{} = b), do: NaiveDateTime.compare(a, b) != :eq end