embracing-nondeterminism-code/src/main/scala/green/thisfieldwas/embracingnondeterminism/control/Applicative.scala

103 lines
3.3 KiB
Scala

package green.thisfieldwas.embracingnondeterminism.control
import green.thisfieldwas.embracingnondeterminism.data.Functor
/** Applicative is a specialization of Functor where the type contained within
* the context, `A` in `F[A]`, in known specifically to have some type of `A =>
* B`. That is, the Functor contains a function and has the form of `F[A =>
* B]`. This make the Functor an ''Applicative'' Functor, which allows it to be
* treated as a lifted function.
*
* @tparam F
* The context type.
*/
trait Applicative[F[_]] extends Functor[F] {
/** Applicative provides a default map() implementation. This can be
* overridden if it makes sense to.
*
* @param fa
* The instance of the structure to apply the function to.
* @param f
* The function to apply, if the structure is in the desired case.
* @tparam A
* The type of instances contained within the structure.
* @tparam B
* The type to map the instances into.
* @return
* The resulting structure.
*/
override def map[A, B](fa: F[A])(f: A => B): F[B] = ap(pure(f))(fa)
/** Lifts the pure value of a computation into the context. This is an
* abstracted constructor for the `Applicative` and concretely for contexts
* `Option` it is `Some`, and for `Either` it is `Right`. It always creates a
* new context in the desired case.
*
* @param a
* The value to lift.
* @tparam A
* The type of the value.
* @return
* A new context in the desired case.
*/
def pure[A](a: A): F[A]
/** Pronounced "apply", this function takes an applicative functor of the form
* `F[A => B]` and applies it to the functor `F[A]`, giving the result of
* `F[B]`.
*
* @param ff
* The applicative functor, or lifted function.
* @param fa
* The argument context, or lifted argument.
* @tparam A
* The type of the argument.
* @tparam B
* The type of the result.
* @return
* The lifted result.
*/
def ap[A, B](ff: F[A => B])(fa: F[A]): F[B]
/** A two-argument analog of `map()`.
*
* @param fa
* The first argument context.
* @param fb
* The second argument context.
* @param f
* The function to apply to the contents of the arguments.
* @tparam A
* The type of the first argument.
* @tparam B
* The type of the second argument.
* @tparam C
* The type of the result.
* @return
* The lifted result.
*/
def map2[A, B, C](fa: F[A], fb: F[B])(f: (A, B) => C): F[C] =
ap(ap(pure(f.curried))(fa))(fb)
/** Take a list of contexts and transform them into a context containing a
* list of their contents. If all contexts are in their desired case, then
* the output context will be a desired case containing a list of their
* contents. Otherwise, the output context will be in the undesired case if
* any context is in the undesired case.
*
* @param listFa
* The list of functors.
* @tparam A
* The type contained within the functors.
* @return
*/
def sequence[A](listFa: List[F[A]]): F[List[A]] =
listFa.foldLeft(pure(List[A]()))((fListA, fa) => map2(fa, fListA)(_ :: _))
}
object Applicative {
def apply[F[_]: Applicative]: Applicative[F] = implicitly[Applicative[F]]
}