kyo.Maybe.scala Maven / Gradle / Ivy
The newest version!
package kyo
import Maybe.*
import Maybe.internal.*
/** Represents an optional value that can be either defined or empty.
*
* @tparam A
* the type of the optional value
*/
opaque type Maybe[+A] >: (Empty | Defined[A]) = Empty | Defined[A]
/** Companion object for Maybe type */
object Maybe:
inline given [A, B](using inline ce: CanEqual[A, B]): CanEqual[Maybe[A], Maybe[B]] = CanEqual.derived
given [A]: Conversion[Maybe[A], IterableOnce[A]] = _.iterator
/** Creates a Maybe instance from a value.
*
* @param v
* the value to wrap
* @tparam A
* the type of the value
* @return
* a Maybe instance containing the value, or Empty if the value is null
*/
def apply[A](v: A): Maybe[A] =
if isNull(v) then Empty
else Defined(v)
/** Converts an Option to a Maybe.
*
* @param opt
* the Option to convert
* @tparam A
* the type of the value
* @return
* a Maybe instance equivalent to the input Option
*/
def fromOption[A](opt: Option[A]): Maybe[A] =
opt match
case Some(v) => Defined(v)
case None => Empty
/** Creates an empty Maybe instance.
*
* @tparam A
* the type parameter of the Maybe
* @return
* an Empty instance
*/
def empty[A]: Maybe[A] = Empty
/** Creates a Maybe instance based on a condition.
*
* @param cond
* the condition to evaluate
* @param v
* the value to wrap if the condition is true
* @tparam A
* the type of the value
* @return
* a Maybe instance containing the value if the condition is true, or Empty otherwise
*/
inline def when[A](cond: Boolean)(inline v: => A): Maybe[A] =
if cond then v else Empty
/** Represents a defined value in a Maybe. */
opaque type Defined[+A] = A | DefinedEmpty
object Defined:
/** Creates a Defined instance.
*
* @param v
* the value to wrap
* @tparam A
* the type of the value
* @return
* a Defined instance containing the value
*/
def apply[A](v: A): Defined[A] =
v match
case v: DefinedEmpty => v.nest
case v: Empty => DefinedEmpty.one
case v => v
/** Extracts the value from a Maybe instance.
*
* @param opt
* the Maybe instance to extract from
* @tparam A
* the type of the value
* @return
* the extracted value wrapped in Maybe.Ops
*/
def unapply[A](opt: Maybe[A]): Maybe.Ops[A] = opt
end Defined
/** Provides operations on Maybe instances. */
implicit final class Ops[A](maybe: Maybe[A]) extends AnyVal:
/** Checks if the Maybe instance is empty.
*
* @return
* true if the instance is empty, false otherwise
*/
def isEmpty: Boolean = maybe.isEmpty
/** Gets the value contained in the Maybe instance.
*
* @return
* the contained value
* @throws NoSuchElementException
* if the instance is empty
*/
def get: A = maybe.get
end Ops
/** Represents an empty Maybe instance. */
sealed abstract class Empty
case object Empty extends Empty
extension [A](self: Maybe[A])
/** Converts the Maybe to an Option.
*
* @return
* an Option containing the value if defined, or None if empty
*/
def toOption: Option[A] =
if isEmpty then None
else Some(get)
/** Checks if the Maybe instance is empty.
*
* @return
* true if the instance is empty, false otherwise
*/
def isEmpty: Boolean =
self.isInstanceOf[Empty]
/** Checks if the Maybe instance is defined.
*
* @return
* true if the instance is defined, false otherwise
*/
inline def isDefined: Boolean = !isEmpty
/** Checks if the Maybe instance is non-empty.
*
* @return
* true if the instance is non-empty, false otherwise
*/
inline def nonEmpty: Boolean = !isEmpty
/** Gets the value contained in the Maybe instance.
*
* @return
* the contained value
* @throws NoSuchElementException
* if the instance is empty
*/
def get: A =
(self: @unchecked) match
case _: Empty =>
throw new NoSuchElementException("Maybe.get")
case self: DefinedEmpty =>
self.unnest.asInstanceOf[A]
case v: A =>
v
/** Gets the value if defined, or returns a default value if empty.
*
* @param default
* the default value to return if empty
* @tparam B
* a supertype of A
* @return
* the contained value if defined, or the default value if empty
*/
inline def getOrElse[B >: A](inline default: => B): B =
if isEmpty then default else get
/** Applies one of two functions depending on whether the Maybe is empty or defined.
*
* @param ifEmpty
* the function to apply if empty
* @param ifDefined
* the function to apply if defined
* @tparam B
* the return type of both functions
* @return
* the result of applying the appropriate function
*/
inline def fold[B](inline ifEmpty: => B)(inline ifDefined: A => B): B =
if isEmpty then ifEmpty else ifDefined(get)
/** Applies a function to the contained value if defined.
*
* @param f
* the function to apply
* @tparam B
* the return type of the function
* @return
* a new Maybe containing the result of the function if defined, or Empty if empty
*/
inline def map[B](inline f: A => B): Maybe[B] =
if isEmpty then Empty else f(get)
/** Applies a function that returns a Maybe to the contained value if defined.
*
* @param f
* the function to apply
* @tparam B
* the type parameter of the resulting Maybe
* @return
* the result of applying the function if defined, or Empty if empty
*/
inline def flatMap[B](inline f: A => Maybe[B]): Maybe[B] =
if isEmpty then Maybe.empty else f(get)
/** Flattens a Maybe of Maybe into a single Maybe.
*
* @param ev
* evidence that A is a subtype of Maybe[B]
* @tparam B
* the type parameter of the inner Maybe
* @return
* the flattened Maybe
*/
inline def flatten[B](using inline ev: A <:< Maybe[B]): Maybe[B] =
if isEmpty then Empty else ev(get)
/** Filters the Maybe based on a predicate.
*
* @param f
* the predicate function
* @return
* the Maybe if it's defined and satisfies the predicate, or Empty otherwise
*/
inline def withFilter(inline f: A => Boolean): Maybe[A] =
filter(f)
/** Filters the Maybe based on a predicate.
*
* @param f
* the predicate function
* @return
* the Maybe if it's defined and satisfies the predicate, or Empty otherwise
*/
inline def filter(inline f: A => Boolean): Maybe[A] =
if isEmpty || f(get) then self else Empty
/** Filters the Maybe based on a negated predicate.
*
* @param f
* the predicate function to negate
* @return
* the Maybe if it's defined and doesn't satisfy the predicate, or Empty otherwise
*/
inline def filterNot(inline f: A => Boolean): Maybe[A] =
if isEmpty || !f(get) then self else Empty
/** Checks if the Maybe contains a specific value.
*
* @param elem
* the value to check for
* @param ev
* evidence of equality between A and B
* @tparam B
* the type of the element to check
* @return
* true if the Maybe is defined and contains the specified value, false otherwise
*/
def contains[B](elem: B)(using CanEqual[A, B]): Boolean =
!isEmpty && get == elem
/** Checks if the Maybe satisfies a predicate.
*
* @param f
* the predicate function
* @return
* true if the Maybe is defined and satisfies the predicate, false otherwise
*/
inline def exists(inline f: A => Boolean): Boolean =
!isEmpty && f(get)
/** Checks if the Maybe satisfies a predicate or is empty.
*
* @param f
* the predicate function
* @return
* true if the Maybe is empty or satisfies the predicate, false otherwise
*/
inline def forall(inline f: A => Boolean): Boolean =
isEmpty || f(get)
/** Applies a side-effecting function to the contained value if defined.
*
* @param f
* the function to apply
*/
inline def foreach(inline f: A => Unit): Unit =
if !isEmpty then f(get)
/** Applies a partial function to the contained value if defined.
*
* @param pf
* the partial function to apply
* @tparam B
* the return type of the partial function
* @return
* a new Maybe containing the result of the partial function if defined and applicable, or Empty otherwise
*/
inline def collect[B](pf: PartialFunction[A, B]): Maybe[B] =
if !isEmpty then
val value = get
if pf.isDefinedAt(value) then
pf(value)
else
Empty
end if
else Empty
/** Returns this Maybe if defined, or an alternative Maybe if empty.
*
* @param alternative
* the alternative Maybe to return if this is empty
* @tparam B
* a supertype of A
* @return
* this Maybe if defined, or the alternative if empty
*/
inline def orElse[B >: A](inline alternative: => Maybe[B]): Maybe[B] =
if isEmpty then alternative else self
/** Combines this Maybe with another Maybe into a tuple.
*
* @param that
* the Maybe to combine with
* @tparam B
* the type parameter of the other Maybe
* @return
* a new Maybe containing a tuple of both values if both are defined, or Empty if either is empty
*/
def zip[B](that: Maybe[B]): Maybe[(A, B)] =
if isEmpty || that.isEmpty then Empty else (get, that.get)
/** Creates an iterator over the contained value.
*
* @return
* an iterator with a single element if defined, or an empty iterator if empty
*/
def iterator: Iterator[A] =
if isEmpty then collection.Iterator.empty else collection.Iterator.single(get)
/** Converts the Maybe to a List.
*
* @return
* a List containing the value if defined, or an empty List if empty
*/
def toList: List[A] =
if isEmpty then List.empty else get :: Nil
/** Converts the Maybe to a Right-biased Either.
*
* @param left
* the value to use for the Left side if this Maybe is empty
* @tparam X
* the type of the Left side
* @return
* a Right containing the value if defined, or a Left containing the provided value if empty
*/
inline def toRight[X](inline left: => X): Either[X, A] =
if isEmpty then Left(left) else Right(get)
/** Converts the Maybe to a Left-biased Either.
*
* @param right
* the value to use for the Right side if this Maybe is empty
* @tparam X
* the type of the Right side
* @return
* a Left containing the value if defined, or a Right containing the provided value if empty
*/
inline def toLeft[X](inline right: => X): Either[A, X] =
if isEmpty then Right(right) else Left(get)
def show: String =
if isEmpty then "Empty"
else s"Defined(${get})"
end extension
private[kyo] object internal:
case class DefinedEmpty(val depth: Int):
def unnest =
if depth > 1 then
DefinedEmpty(depth - 1)
else
Empty
def nest =
DefinedEmpty(depth + 1)
override def toString: String =
"Defined(" * depth + "Empty" + ")" * depth
end DefinedEmpty
object DefinedEmpty:
val cache = (0 until 100).map(new DefinedEmpty(_)).toArray
val one = DefinedEmpty(1)
def apply(depth: Int): DefinedEmpty =
if depth < cache.length then
cache(depth)
else
new DefinedEmpty(depth)
end DefinedEmpty
end internal
end Maybe