import TypeClass defclass Witchcraft.Monoid do @moduledoc ~S""" Monoid extends the semigroup with the concept of an "empty" or "zero" element. ## Type Class An instance of `Witchcraft.Monoid` must also implement `Witchcraft.Semigroup`, and define `Witchcraft.Monoid.empty/1`. Semigroup [append/2] ↓ Monoid [empty/1] """ alias __MODULE__ extend Witchcraft.Semigroup, alias: true use Witchcraft.Internal, deps: [Witchcraft.Semigroup] @type t :: any() where do @doc ~S""" An "emptied out" or "starting position" of the passed data. ## Example iex> empty(10) 0 iex> empty [1, 2, 3, 4, 5] [] """ def empty(sample) end defalias zero(sample), as: :empty @doc """ Check if a value is the empty element of that type. ## Examples iex> empty?([]) true iex> empty?([1]) false """ @spec empty?(Monoid.t()) :: boolean def empty?(monoid), do: empty(monoid) == monoid properties do def left_identity(data) do a = generate(data) if is_function(a) do equal?(Semigroup.append(Monoid.empty(a), a).("foo"), a.("foo")) else equal?(Semigroup.append(Monoid.empty(a), a), a) end end def right_identity(data) do a = generate(data) if is_function(a) do Semigroup.append(a, Monoid.empty(a)).("foo") == a.("foo") else Semigroup.append(a, Monoid.empty(a)) == a end end end end definst Witchcraft.Monoid, for: Function do def empty(sample) when is_function(sample), do: &Quark.id/1 end definst Witchcraft.Monoid, for: Integer do def empty(_), do: 0 end definst Witchcraft.Monoid, for: Float do def empty(_), do: 0.0 end definst Witchcraft.Monoid, for: BitString do def empty(_), do: "" end definst Witchcraft.Monoid, for: List do def empty(_), do: [] end definst Witchcraft.Monoid, for: Map do def empty(_), do: %{} end definst Witchcraft.Monoid, for: Tuple do custom_generator(_) do {} end def empty(sample), do: Witchcraft.Functor.map(sample, &Witchcraft.Monoid.empty/1) end definst Witchcraft.Monoid, for: MapSet do def empty(_), do: MapSet.new() end definst Witchcraft.Monoid, for: Witchcraft.Unit do require Witchcraft.Semigroup def empty(_), do: %Witchcraft.Unit{} end