import TypeClass
defclass Witchcraft.Applicative do
@moduledoc """
`Applicative` extends `Apply` with the ability to lift value into a
particular data type or "context".
This fills in the connection between regular function application and `Apply`
data --------------- function ---------------> result
| | |
of(Container, data) of(Container, function) of(Container, result)
↓ ↓ ↓
%Container<data> --- %Container<function> ---> %Container<result>
## Type Class
An instance of `Witchcraft.Applicative` must also implement `Witchcraft.Apply`,
and define `Witchcraft.Applicative.of/2`.
Functor [map/2]
↓
Apply [convey/2]
↓
Applicative [of/2]
"""
alias __MODULE__
extend Witchcraft.Apply
use Witchcraft.Internal, deps: [Witchcraft.Apply]
@type t :: any()
where do
@doc """
Bring a value into the same data type as some sample
## Examples
iex> of([], 42)
[42]
iex> of([1, 2, 3], 42)
[42]
iex> of({"a", "b", 155}, 42)
{"", "", 42}
iex> of(fn -> nil end, 42).(55)
42
iex> of(fn(a, b, c) -> a + b - c end, 42).(55)
42
iex> import Witchcraft.Apply
...>
...> []
...> |> of(&+/2)
...> |> provide([1, 2, 3])
...> |> ap(of([], 42))
[43, 44, 45]
"""
@spec of(Applicative.t(), any()) :: Applicative.t()
def of(sample, to_wrap)
end
@doc """
Partially apply `of/2`, generally as a way to bring many values into the same context.
## Examples
iex> {"very example", "much wow"}
...> |> of()
...> |> Witchcraft.Functor.across([42, "hello", [1, 2, 3]])
[{"", 42}, {"", "hello"}, {"", [1, 2, 3]}]
"""
@spec of(Applicative.t()) :: (any() -> Applicative.t())
def of(sample), do: fn to_wrap -> of(sample, to_wrap) end
@doc """
Alias for `of/2`, for cases that this helps legibility or style.
## Example
iex> wrap({":|", "^.~"}, 42)
{"", 42}
iex> [] |> wrap(42)
[42]
"""
@spec wrap(Applicative.t(), any()) :: Applicative.t()
defalias wrap(sample, to_wrap), as: :of
@doc """
Alias for `of/2`, for cases that this helps legibility or style.
## Example
iex> pure({"ohai", "thar"}, 42)
{"", 42}
iex> [] |> pure(42)
[42]
"""
@spec pure(Applicative.t(), any()) :: Applicative.t()
defalias pure(sample, to_wrap), as: :of
@doc """
`of/2` with arguments reversed.
## Example
iex> to(42, {"ohai", "thar"})
{"", 42}
iex> 42 |> to([])
[42]
42 |> to(%Algae.Id{})
#=> %Algae.Id{id: 42}
"""
@spec to(any(), Applicative.t()) :: Applicative.t()
def to(to_wrap, sample), do: of(sample, to_wrap)
@doc """
Prime a value to be brought into other data types
## Example
iex> make = to(42)
...> make.({"ohai", "thar"})
{"", 42}
...> make.([])
[42]
"""
@spec to(any()) :: (Applicative.t() -> Applicative.t())
def to(to_wrap), do: fn type -> of(type, to_wrap) end
@doc """
Alias for `of/2`, for cases that this helps legibility or style
## Example
iex> unit({":)", ":("}, 42)
{"", 42}
iex> [] |> unit(42)
[42]
"""
@spec unit(Applicative.t(), any()) :: Applicative.t()
defalias unit(sample, to_wrap), as: :of
properties do
import Witchcraft.Functor
import Witchcraft.Apply
def identity(data) do
a = generate(data)
f = &Quark.id/1
equal?(a, a ~>> Applicative.of(a, f))
end
def homomorphism(data) do
arg = 42
a = generate(data)
f = &inspect/1
left = Applicative.of(a, arg) ~>> Applicative.of(a, f)
right = Applicative.of(a, f.(arg))
equal?(left, right)
end
def interchange(data) do
arg = 42
as = generate(data)
fs = replace(as, &inspect/1)
left = Applicative.of(as, arg) ~>> fs
right = fs ~>> Applicative.of(as, fn g -> g.(arg) end)
equal?(left, right)
end
end
end
definst Witchcraft.Applicative, for: Function do
def of(_, unwrapped), do: &Quark.SKI.k(unwrapped, &1)
end
definst Witchcraft.Applicative, for: List do
def of(_, unwrapped), do: [unwrapped]
end
definst Witchcraft.Applicative, for: Tuple do
custom_generator(_) do
import TypeClass.Property.Generator, only: [generate: 1]
{generate(0), generate(0)}
end
def of(sample, unwrapped) do
size = tuple_size(sample)
sample
|> elem(0)
|> Witchcraft.Monoid.empty()
|> Tuple.duplicate(size)
|> put_elem(size - 1, unwrapped)
end
end