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

75 lines
2.9 KiB
Scala

package green.thisfieldwas.embracingnondeterminism.control
/** Monad is a specialization of Functor where the type contained within the
* context, `A` in `F[A]`, is known specifically to have some type of `F[A]`.
* That is, the Functor is nested as `F[F[A]]`. The Monad defines a function
* called `flatten()` which joins the inner `F[A]` with the outer `F[_]`, and
* defines a specialization of the `map()` function called `flatMap()` which
* permits the usage of a function producing a context: `f: A => F[B]`.
*
* By permitting the context to nest within itself, the inner context may alter
* the outer context's case when flattened. This allows for the function `f: A
* \=> F[B]` to control whether computation proceeds or halts against the
* context.
*
* Monads must define one or both of `flatten()` or `flatMap()`, as one is used
* to define the other.
*
* @tparam F
* The context type.
*/
trait Monad[F[_]] extends Applicative[F] {
/** Map the contents of this context with a function which returns its results
* in a new instance of the context. This new context is joined with the
* current context.
*
* If the context `fa` is in the desired case, two things can happen based on
* the output of function `f`:
*
* 1. If `f` returns `F[B]` in the desired case, then `flatMap()` returns
* `F[B]` in the desired case.
* 1. If `f` returns `F[B]` in the undesired case, then `flatMap()` returns
* `F[B]` in the undesired case and further computation is halted.
*
* If the context `fa` is in the undesired case, then `f` does not apply and
* no computation occurs.
*
* @param fa
* The context to map.
* @param f
* The function to map the contents of the context.
* @tparam A
* The term of the instances within the context.
* @tparam B
* The term of the instances within the context produced by the function.
* @return
* The context produced by the function joined with the context `fa`.
*/
def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B] = flatten(map(fa)(f))
/** Flattens a nested context.
*
* Based on the states of the contexts, a few things may happen:
* 1. If the outer context is in the undesired case, then a flattened
* context in the undesired case is produced.
* 1. If the inner context is in the undesired case, then a flattened
* context in the undesired case is produced. 3. If both contexts are in
* the desired case, then a flattened context in the desired case is
* produced.
*
* @param ffa
* The nested context.
* @tparam A
* The term contained by the inner context.
* @return
* A flattened context containing term `A`.
*/
def flatten[A](ffa: F[F[A]]): F[A] = flatMap(ffa)(fa => fa)
}
object Monad {
def apply[F[_]: Monad]: Monad[F] = implicitly[Monad[F]]
}