Realm v0.1.0 Realm.Apply protocol View Source
An extension of Realm.Functor
, Apply
provides a way to apply arguments
to functions when both are apply in the same kind of container. This can be
seen as running function application "in a context".
For a nice, illustrated introduction,
see Functors, Applicatives, And Monads In Pictures.
Graphically
If function application looks like this
data |> function == result
and a functor looks like this
%Container<data> ~> function == %Container<result>
then an apply looks like
%Container<data> ~>> %Container<function> == %Container<result>
which is similar to function application inside containers, plus the ability to attach special effects to applications.
data --------------- function ---------------> result
%Container<data> --- %Container<function> ---> %Container<result>
This lets us do functorial things like
- continue applying values to a curried function resulting from a
Realm.Functor.map/2
- apply multiple functions to multiple arguments (with lists)
- propogate some state (like
Nothing
inAlgae.Maybe
) but now with a much larger number of arguments, reuse partially applied functions, and run effects with the function container as well as the data container.
Examples
iex> ap([fn x -> x + 1 end, fn y -> y * 10 end], [1, 2, 3])
[2, 3, 4, 10, 20, 30]
iex> [100, 200]
...> |> Realm.Functor.map(curry(fn)(x, y, z) -> x * y / z end)
...> |> provide([5, 2])
...> |> provide([100, 50])
[5.0, 10.0, 2.0, 4.0, 10.0, 20.0, 4.0, 8.0]
# ↓ ↓
# 100 * 5 / 100 200 * 5 / 50
iex> import Realm.Functor
...>
...> [100, 200]
...> ~> fn(x, y, z) ->
...> x * y / z
...> end <<~ [5, 2]
...> <<~ [100, 50]
[5.0, 10.0, 2.0, 4.0, 10.0, 20.0, 4.0, 8.0]
# ↓ ↓
# 100 * 5 / 100 200 * 5 / 50
%Algae.Maybe.Just{just: 42}
~> fn(x, y, z) ->
x * y / z
end <<~ %Algae.Maybe.Nothing{}
<<~ %Algae.Maybe.Just{just: 99}
#=> %Algae.Maybe.Nothing{}
convey
vs ap
convey
and ap
essentially associate in opposite directions. For example,
large data is usually more efficient with ap
, and large numbers of
functions are usually more efficient with convey
.
It's also more consistent consistency. In Elixir, we like to think of a "subject"
being piped through a series of transformations. This places the function argument
as the second argument. In Realm.Functor
, this was of little consequence.
However, in Apply
, we're essentially running superpowered function application.
ap
is short for apply
, as to not conflict with Kernel.apply/2
, and is meant
to respect a similar API, with the function as the first argument. This also reads
nicely when piped, as it becomes [funs] |> ap([args1]) |> ap([args2])
,
which is similar in structure to fun.(arg2).(arg1)
.
With potentially multiple functions being applied over potentially
many arguments, we need to worry about ordering. convey
not only flips
the order of arguments, but also who is in control of ordering.
convey
typically runs each function over all arguments (first_fun ⬸ all_args
),
and ap
runs all functions for each element (first_arg ⬸ all_funs
).
This may change the order of results, and is a feature, not a bug.
iex> [1, 2, 3]
...> |> Realm.Apply.convey([&(&1 + 1), &(&1 * 10)])
[
2, 10, # [(1 + 1), (1 * 10)]
3, 20, # [(2 + 1), (2 * 10)]
4, 30 # [(3 + 1), (3 * 10)]
]
iex> [&(&1 + 1), &(&1 * 10)]
...> |> ap([1, 2, 3])
[
2, 3, 4, # [(1 + 1), (2 + 1), (3 + 1)]
10, 20, 30 # [(1 * 10), (2 * 10), (3 * 10)]
]
Type Class
An instance of Realm.Apply
must also implement Realm.Functor
,
and define Realm.Apply.convey/2
.
Functor [map/2]
↓
Apply [convey/2]
Link to this section Summary
Functions
Pipe arguments to functions, when both are apply in the same type of data structure.