import TypeClass defclass Witchcraft.Ord do @moduledoc ~S""" `Ord` describes how to order elements of a data type. This is a total order, so all elements are either `:equal`, `:greater`, or `:lesser` than each other. ## Type Class An instance of `Witchcraft.Ord` must also implement `Witchcraft.Setoid`, and define `Witchcraft.Ord.compare/2`. Setoid [equivalent?/2] ↓ Ord [compare/2] """ alias __MODULE__ extend Witchcraft.Setoid use Witchcraft.Internal, overrides: [<: 2, >: 2, <=: 2, >=: 2] @type t :: any() @type ordering :: :lesser | :equal | :greater where do @doc """ Get the ordering relationship between two elements. Possible results are `:lesser`, `:equal`, and `:greater` ## Examples iex> compare(1, 1) :equal iex> compare([1], [2]) :lesser iex> compare([1, 2], [3]) :lesser iex> compare([3, 2, 1], [1, 2, 3, 4, 5]) :greater """ @spec compare(Ord.t(), Ord.t()) :: Ord.ordering() def compare(ord_a, ord_b) end properties do def reflexivity(data) do a = generate(data) comparison = Ord.compare(a, a) equal?(comparison, :equal) or equal?(comparison, :lesser) end def transitivity(data) do x = generate(data) y = generate(data) z = generate(data) x_y = Ord.compare(x, y) y_z = Ord.compare(y, z) x_z = Ord.compare(x, z) if x_y != :greater and y_z != :greater do equal?(x_z, :lesser) or equal?(x_z, :equal) else true end end def antisymmetry(data) do a = generate(data) b = generate(data) a_b = Ord.compare(a, b) b_a = Ord.compare(b, a) if a_b != :greater and b_a != :greater, do: a_b == :equal, else: true end end @doc """ Determine if two elements are `:equal`. ## Examples iex> equal?(1, 1.0) true iex> equal?(1, 2) false """ @spec equal?(Ord.t(), Ord.t()) :: boolean() def equal?(a, b), do: compare(a, b) == :equal @doc """ Determine if an element is `:greater` than another. ## Examples iex> greater?(1, 1) false iex> greater?(1.1, 1) true """ @spec greater?(Ord.t(), Ord.t()) :: boolean() def greater?(a, b), do: compare(a, b) == :greater defalias a > b, as: :greater? @doc """ Determine if an element is `:lesser` than another. ## Examples iex> lesser?(1, 1) false iex> lesser?(1, 1.1) true """ @spec lesser?(Ord.t(), Ord.t()) :: boolean() def lesser?(a, b), do: compare(a, b) == :lesser defalias a < b, as: :lesser? @doc """ Determine if an element is `:lesser` or `:equal` to another. ## Examples iex> use Witchcraft.Ord ...> 1 <= 2 true ...> [] <= [1, 2, 3] false ...> [1] <= [1, 2, 3] true ...> [4] <= [1, 2, 3] false """ # credo:disable-for-next-line Credo.Check.Warning.OperationOnSameValues @spec Ord.t() <= Ord.t() :: boolean() def a <= b, do: compare(a, b) != :greater @doc """ Determine if an element is `:greater` or `:equal` to another. ## Examples iex> use Witchcraft.Ord ...> 2 >= 1 true ...> [1, 2, 3] >= [] true ...> [1, 2, 3] >= [1] true ...> [1, 2, 3] >= [4] false """ # credo:disable-for-next-line Credo.Check.Warning.OperationOnSameValues @spec Ord.t() >= Ord.t() :: boolean() def a >= b, do: compare(a, b) != :lesser end