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

197 lines
5.6 KiB
Scala

package green.thisfieldwas.embracingnondeterminism.data
import green.thisfieldwas.embracingnondeterminism.control.Applicative
/** The `Option` class encodes a dimension contextualized by the presence or
* absence of an instance of the term `A`. The presence of `A` is unknown at
* runtime without specific knowledge of the inputs to the operation producing
* the context.
*
* This context is in the desired case if an instance of `A` is present. The
* absence of `A` is the undesired case.
*
* This context may be regarded as a "null done right", as it strongly-types
* the notion of absence and requires that the absent case be handled.
*
* @tparam A
* The type of the instance contained.
*/
sealed trait Option[+A] {
/** Gets the instance of term `A` if it exists.
*
* @return
* The instance of `A`.
* @throws NoSuchElementException
* if the instance of `A` is absent.
*/
def get: A
/** Gets whether the instance of `A` is present.
*
* @return
* True if present.
*/
def isSome: Boolean = !isNone
/** Gets whether the instance of `A` is absent.
*
* @return
* True if absent.
*/
def isNone: Boolean = this == None
/** Fold the `Option` into a single value.
*
* @param noneCase
* The value to use if the instance of term `A` is absent.
* @param someCase
* The function to transform the instance of term `A` if present.
* @tparam B
* The type to transform term `A` into.
* @return
* The transformed instance of `A`.
*/
def fold[B](noneCase: => B)(someCase: A => B): B
/** If this option is empty, use the other option.
*
* @param other
* The other option to use.
* @tparam B
* The type of the other option, compatible with this one.
* @return
* The option chosen.
*/
def orElse[B >: A](other: Option[B]): Option[B]
}
/** Encodes the present case of an instance of term `A`. That is to say, there
* is "Some" `A` here.
*
* @param get
* The instance of term `A`.
* @tparam A
* The type of the instance contained.
*/
case class Some[+A](get: A) extends Option[A] {
override def fold[B](noneCase: => B)(someCase: A => B): B = someCase(get)
override def orElse[B >: A](other: Option[B]): Option[B] = this
}
/** Encodes the absent ccase of an instance of term `A`. That is, if you were to
* inspect for some instance of term `A`, you would see "None" here.
*/
case object None extends Option[Nothing] {
override def get: Nothing = throw new NoSuchElementException("None.get")
override def fold[B](noneCase: => B)(someCase: Nothing => B): B = noneCase
override def orElse[B >: Nothing](other: Option[B]): Option[B] = other
}
object Option {
/** A smart constructor for a "Some" typed specifically as an `Option`.
*
* @param a
* The present instance of term `A`.
* @tparam A
* The type of the instance contained.
* @return
* An `Option` in the desired case with a present instance of term `A`.
*/
def apply[A](a: A): Option[A] = Some(a)
/** A smart constructor for a "None" typed specifically as an `Option`.
*
* @tparam A
* The type of the instance contained.
* @return
* An `Option` in the undesired case with an absent instance of term `A`.
*/
def apply[A](): Option[A] = None
/** Allows for idiomatic pattern matching syntax:
*
* {{{
* option match {
* case Some(x) => ???
* case None => ???
* }
* }}}
*
* @param o
* The option to unapply.
* @tparam A
* The type of the instance contained.
* @return
* A Scala `Option` corresponding to this `Option`.
*/
def unapply[A](o: Option[A]): scala.Option[A] = o match {
case Some(a) => scala.Some(a)
case None => scala.None
}
/** Given a truthy condition, lift an instance of term `A` into the `Option`,
* giving the desired `Some` case. If the condition is falsy, then return the
* undesired `None` case.
*
* @param test
* The condition to test.
* @param presentCase
* The value to lift into the present case if the condition is true.
* @tparam A
* The type of the instance.
* @return
* The lifted value if the condition is true and `None` otherwise.
*/
def cond[A](test: => Boolean)(presentCase: => A): Option[A] =
if (test) {
Option(presentCase)
} else {
None
}
implicit val optionApplicative: Applicative[Option] = new Applicative[Option] {
/** 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): Option[A] = Some(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: Option[A => B])(fa: Option[A]): Option[B] =
(ff, fa) match {
case (Some(f), Some(a)) => Some(f(a))
case _ => None
}
}
}