153 lines
4.3 KiB
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)
|
|
}
|
|
}
|
|
}
|