176 lines
5.1 KiB
Scala
176 lines
5.1 KiB
Scala
package green.thisfieldwas.embracingnondeterminism.data




import green.thisfieldwas.embracingnondeterminism.control.Monad




/** 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 eitherMonad[X]: Monad[Either[X, *]] = new Monad[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)


}




/** 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`.


*/


override def flatten[A](ffa: Either[X, Either[X, A]]): Either[X, A] =


ffa match {


case Right(fa) => fa


case Left(x) => Left(x)


}


}


}
