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

128 lines
3.6 KiB
Scala

package green.thisfieldwas.embracingnondeterminism.data
/** 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
}
/** 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]
/** 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")
}
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
}
implicit val optionFunctor: Functor[Option] = new Functor[Option] {
/** 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: Option[A])(f: A => B): Option[B] = fa match {
case Some(a) => Some(f(a))
case None => None
}
}
}