import TypeClass defclass Witchcraft.Comonad do @moduledoc """ The dual of monads, `Comonad` brings an unwrapping function to `Extend`able data. Note that the unwrapping function (`extract`) *must return a value*, and is not available on many data structres that have an empty element. For example, there is no `Comonad` instance for `List` because we cannot pull a value out of `[]`. ## Type Class An instance of `Witchcraft.Comonad` must also implement `Witchcraft.Extend`, and define `Witchcraft.Comonad.extract/1`. Functor [map/2] ↓ Extend [nest/1] ↓ Comonad [extract/1] """ alias __MODULE__ extend Witchcraft.Extend use Witchcraft.Internal, deps: [Witchcraft.Extend] use Quark @type t :: any() where do @doc """ Extract a value out of some context / data structure. This is the opposite of `Witchcraft.Applicative.of/2`. ## Examples iex> extract({1, 2}) 2 extract(%Id{id: 42}) #=> 42 """ @spec extract(Comonad.t()) :: any() def extract(nested) end @doc """ Alias for `extract/1` ## Examples iex> unwrap({1, 2}) 2 unwrap(%Id{id: 42}) #=> 42 """ @spec unwrap(Comonad.t()) :: any() def unwrap(nested), do: extract(nested) properties do def left_identity(data) do a = generate(data) a |> Witchcraft.Extend.extend(&Comonad.extract/1) |> equal?(a) end def right_identity(data) do a = generate(data) f = fn x -> x |> Comonad.extract() |> inspect() end a |> Witchcraft.Extend.extend(f) |> Comonad.extract() |> equal?(f.(a)) end end end definst Witchcraft.Comonad, for: Tuple do custom_generator(_) do import TypeClass.Property.Generator, only: [generate: 1] {generate(nil), generate(nil)} end def extract(tuple) when tuple_size(tuple) >= 1 do elem(tuple, tuple_size(tuple) - 1) end end