embracing-nondeterminism-code/src/main/scala/green/thisfieldwas/embracingnondeterminism/data/Either.scala

153 lines
4.3 KiB
Scala

package green.thisfieldwas.embracingnondeterminism.data
import green.thisfieldwas.embracingnondeterminism.control.Applicative
/** The `Either` class encodes a dimension contextualized by "Either" the
* "Right" term that you want, or you're "Left" with the term you don't want.
* This context is frequently used to model success or failure, the specific
* instance of which is unknown at runtime without specific knowledge of the
* inputs to the operation producing the context.
*
* The presence of the "Right" term `A` is the desired case of this context. If
* the "Left" term `X` is present, then the context is in the undesired case.
*
* @tparam X
* The "Left" term, or undesired case.
* @tparam A
* The "Right" term, or desired case.
*/
sealed trait Either[+X, +A] {
/** Gets the instance of the "Left" term if it exists.
*
* @return
* The "Left" instance.
* @throws NoSuchElementException
* if the context is a "Right".
*/
def left: X
/** Gets the instance of the "Right" term if it exists.
*
* @return
* The "Right" instance.
* @throws NoSuchElementException
* if the context is a "Left".
*/
def right: A
/** Gets whether this context is a "Left".
*
* @return
* True if left.
*/
def isLeft: Boolean = false
/** Gets whether this context is a "Right".
*
* @return
* True if right.
*/
def isRight: Boolean = false
}
/** Encodes the "Left" or undesired case of the context.
*
* @param left
* The instance of the "Left" term.
* @tparam X
* The "Left" term, or undesired case.
* @tparam A
* The "Right" term, or desired case.
*/
case class Left[+X, +A](left: X) extends Either[X, A] {
def right: A = throw new NoSuchElementException("Left.right")
override def isLeft: Boolean = true
}
/** Encodes the "Right" or desired case of the context.
*
* @param right
* The instance of the "Right" case.
* @tparam X
* The "Left" term, or undesired case.
* @tparam A
* The "Right" term, or desired case.
*/
case class Right[+X, +A](right: A) extends Either[X, A] {
def left: X = throw new NoSuchElementException("Right.left")
override def isRight: Boolean = true
}
object Either {
/** A smart constructor to create a "Left" typed specifically as an "Either".
*
* @param x
* The "Left" instance.
* @tparam X
* The "Left" term, or undesired case.
* @tparam A
* The "Right" term, or desired case.
* @return
* An "Either" in the undesired case.
*/
def left[X, A](x: X): Either[X, A] = Left(x)
/** A smart constructor to create a "Right" typed specifically as an "Either".
*
* @param a
* The "Right" instance.
* @tparam X
* The "Left" term, or undesired case.
* @tparam A
* The "Right" term, or desired case.
* @return
* An "Either" in the desired case.
*/
def right[X, A](a: A): Either[X, A] = Right(a)
implicit def eitherApplicative[X]: Applicative[Either[X, *]] = new Applicative[Either[X, *]] {
/** 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.
*/
override def pure[A](a: A): Either[X, A] = Right(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.
*/
override def ap[A, B](ff: Either[X, A => B])(fa: Either[X, A]): Either[X, B] =
(ff, fa) match {
case (Right(f), Right(a)) => Right(f(a))
case (Left(x), _) => Left(x)
case (_, Left(x)) => Left(x)
}
}
}