197 lines
5.6 KiB
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
|
|
}
|
|
}
|
|
}
|