Skip to content

Instantly share code, notes, and snippets.

@johnhungerford
Last active November 10, 2023 18:35
Show Gist options
  • Save johnhungerford/b3ccf0f46783db765688dba19bfba4a3 to your computer and use it in GitHub Desktop.
Save johnhungerford/b3ccf0f46783db765688dba19bfba4a3 to your computer and use it in GitHub Desktop.
Zero overhead Option in Scala 3
//> using scala 3.3.0
import scala.util.{Try, Success, Failure}
import scala.reflect.Typeable
import scala.util.NotGiven
import scala.annotation.implicitNotFound
type Opt[T] = Opt.Opt[T]
object Opt:
opaque type Opt[+T] = T | Null
def apply[T](value: T): Opt[T] = value
val None: Opt[Nothing] = null
object Some:
def apply[T](value: T): Opt[T] = value
extension [T](nullable: Opt[T])
def isEmpty: Boolean = nullable == null
def isDefined: Boolean = !isEmpty
def nonEmpty: Boolean = isDefined
def fold[U](default: => U)(f: T => U): U =
if nullable == null then default
else f(nullable.asInstanceOf[T])
def contains(t: T): Boolean = fold(false)(_ == t)
def exists(f: T => Boolean): Boolean = fold(false)(f)
def flatten[U](using hasInnerOpt: T =:= Opt[U]): Opt[U] =
fold(None)(t => hasInnerOpt(t))
def toStringTagged: String =
fold("None")(v => s"Some($v)")
def map[U](f: T => U): Opt[U] =
flatMap(f)
def flatMap[U](f: T => Opt[U]): Opt[U] =
if nullable == null then null else f(nullable.asInstanceOf[T])
def zip[U](other: => Opt[U]): Opt[(T, U)] =
flatMap(t => other.flatMap(u => t -> u))
def getOrElse[U >: T](u: => U): U =
if nullable == null then u else nullable.asInstanceOf[T]
def get : T =
nullable.getOrElse(throw new NullPointerException())
def toOption: Option[T] = fold(scala.None)(scala.Some.apply)
def toRight[L](left: => L): Either[L, T] =
if nullable == null then Left(left) else Right(nullable.asInstanceOf[T])
def toLeft[R](right: => R): Either[T, R] =
if nullable == null then Right(right) else Left(nullable.asInstanceOf[T])
def toList: List[T] = fold(Nil)(_ :: Nil)
def toSet: Set[T] = fold(Set.empty[T])(t => Set(t))
def toMap[K, V](using T =:= (K, V)): Map[K, V] =
fold(Map.empty)(tup => Map(tup))
def toVector: Vector[T] = fold(Vector.empty)(t => Vector(t))
object syntax:
extension[L, R](either: Either[L, R])
def toOpt: Opt[R] = either match
case Left(_) => null
case Right(value) => value
def toOptLeft: Opt[L] = either match
case Left(value) => value
case Right(_) => null
extension[T](_try: Try[T])
def toOpt: Opt[T] = _try match
case Success(value) => value
case Failure(_) => null
def toOptFail: Opt[Throwable] = _try match
case Success(_) => null
case Failure(err) => err
extension[T](option: Option[T])
def toOpt: Opt[T] = option.fold(Opt.None)(Opt.apply)
object Main:
def main(args: Array[String]): Unit =
import Opt.syntax.*
val a : Opt[String] = Opt("hi")
val b : Opt[String] = Opt(null)
val c : Opt[String] = Opt("what")
val d : Opt[String] = Right("hello").toOpt
val e : Opt[String] = Left('c').toOpt
println(a.toStringTagged)
println(b.toStringTagged)
println(c.toStringTagged)
println(d.toStringTagged)
println(e.toStringTagged)
val res1 = for {
aVal <- a
bVal <- b
} yield aVal + aVal
val res2 = for {
aVal <- a
cVal <- c
} yield aVal + cVal
println(res1.toStringTagged)
println(res2.toStringTagged)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment