defmodule Quark.SKI do
@moduledoc ~S"""
The classic [SKI](https://en.wikipedia.org/wiki/SKI_combinator_calculus)
system of combinators. `s` and `k` alone can be used to express any algorithm,
though generally not efficiently.
"""
import Quark.Partial
@doc ~S"""
The identity combinator. Also aliased as `id`.
iex> i(1)
1
iex> i("identity combinator")
"identity combinator"
iex> [1,2,3] |> id
[1,2,3]
"""
@spec i(any) :: any
defpartial i(x), do: x
defdelegate id(x), to: __MODULE__, as: :i
@doc ~S"""
The constant ("Konstant") combinator. Returns the first argument unchanged,
and discards the second argument.
Can be used to repeatedly apply the same value in functions such as folds.
Aliased as `first` and `constant`.
## Examples
iex> k(1, 2)
1
iex> k("happy", "sad")
"happy"
iex> Enum.reduce([1,2,3], [42], &k/2)
3
iex> Enum.reduce([1,2,3], [42], &constant/2)
3
iex> first(1,2)
1
"""
@spec k(any, any) :: any
defpartial k(x, y), do: if y === y, do: x
defdelegate constant(a, b), to: __MODULE__, as: :k
defdelegate first(a, b), to: __MODULE__, as: :k
@doc ~S"""
Opposite of `first` (the `k` combinator).
While not strictly part of SKI, it's a common enough case.
Returns the *second* of two arguments. Can be used to repeatedly apply
the same value in functions such as folds.
## Examples
iex> second(43, 42)
42
iex> Enum.reduce([1,2,3], [], &second/2)
[]
"""
@spec second(any, any) :: any
defpartial second(a, b), do: if a === a, do: b
@doc ~S"""
The "substitution" combinator. Applies the last argument to the first two,
and then the first two to each other.
## Examples
iex> add = &(&1 + &2)
...> double = &(&1 * 2)
...> s(add, double, 8)
24
"""
@spec s(fun, fun, any) :: any
defpartial s(x, y, z) do
sub_x = &x.(z, &1)
sub_y = y.(z)
sub_x.(sub_y)
end
end