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