import TypeClass
defclass Witchcraft.Setoid do
@moduledoc ~S"""
A setoid is a type with an equivalence relation.
This is most useful when equivalence of some data is not the same as equality.
Since some types have differing concepts of equality, this allows overriding
the behaviour from `Kernel.==/2`. To get the Setoid `==` operator override,
simply `use Witchcraft.Setoid`.
## Type Class
An instance of `Witchcraft.Setoid` must define `Witchcraft.Setoid.equivalent?/2`
Setoid [equivalent?/2]
"""
alias __MODULE__
use Witchcraft.Internal, overrides: [==: 2, !=: 2]
@type t :: any()
where do
@doc ~S"""
Compare two setoids and determine if they are equivalent.
Aliased as `==`.
## Examples
iex> equivalent?(1, 2)
false
iex> import Kernel, except: [==: 2, !=: 2]
...> %{a: 1} == %{a: 1, b: 2}
false
equivalent?(%Maybe.Just{just: 42}, %Maybe.Nothing{})
#=> false
### Equivalence not equality
baby_harry = %Wizard{name: "Harry Potter", age: 10}
old_harry = %Wizard{name: "Harry Potter", age: 17}
def chosen_one?(some_wizard), do: equivalent?(baby_harry, some_wizard)
chosen_one?(old_harry)
#=> true
"""
@spec equivalent?(Setoid.t(), Setoid.t()) :: boolean()
def equivalent?(a, b)
end
defalias a == b, as: :equivalent?
@doc """
The opposite of `equivalent?/2`.
## Examples
iex> nonequivalent?(1, 2)
true
"""
@spec nonequivalent?(Setoid.t(), Setoid.t()) :: boolean()
def nonequivalent?(a, b), do: not equivalent?(a, b)
defalias a != b, as: :nonequivalent?
properties do
def reflexivity(data) do
a = generate(data)
Setoid.equivalent?(a, a)
end
def symmetry(data) do
a = generate(data)
b = generate(data)
equal?(Setoid.equivalent?(a, b), Setoid.equivalent?(b, a))
end
def transitivity(data) do
a = b = c = generate(data)
Setoid.equivalent?(a, b) and Setoid.equivalent?(b, c) and Setoid.equivalent?(a, c)
end
end
end
definst Witchcraft.Setoid, for: Integer do
def equivalent?(int, num), do: Kernel.==(int, num)
end
definst Witchcraft.Setoid, for: Float do
def equivalent?(float, num), do: Kernel.==(float, num)
end
definst Witchcraft.Setoid, for: BitString do
def equivalent?(string_a, string_b), do: Kernel.==(string_a, string_b)
end
definst Witchcraft.Setoid, for: Tuple do
def equivalent?(tuple_a, tuple_b), do: Kernel.==(tuple_a, tuple_b)
end
definst Witchcraft.Setoid, for: List do
def equivalent?(list_a, list_b), do: Kernel.==(list_a, list_b)
end
definst Witchcraft.Setoid, for: Map do
def equivalent?(map_a, map_b), do: Kernel.==(map_a, map_b)
end
definst Witchcraft.Setoid, for: MapSet do
def equivalent?(a, b), do: MapSet.equal?(a, b)
end