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

136 lines
3.7 KiB
Scala

package green.thisfieldwas.embracingnondeterminism.data
/** 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 eitherFunctor[X]: Functor[Either[X, *]] = new Functor[Either[X, *]] {
/** Given a structure `F[A]` and function `f: A => B`, if the structure is
* in the desired case, then apply the function such that `F[A] => F[B]`.
* If the structure `F[A]` is in the undesired case, then propagate the
* undesired case as `F[B]`.
*
* @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: Either[X, A])(f: A => B): Either[X, B] = fa match {
case Left(x) => Left(x)
case Right(a) => Right(f(a))
}
}
}