tabdulradi/nullable
{ "createdAt": "2019-03-30T18:33:23Z", "defaultBranch": "master", "description": "Makes `A | Null` work with for-comprehensions", "fullName": "tabdulradi/nullable", "homepage": "", "language": "Scala", "name": "nullable", "pushedAt": "2024-02-11T22:29:12Z", "stargazersCount": 23, "topics": [ "allocation-cost", "dotty", "null-safety", "scala", "union-types" ], "updatedAt": "2025-08-15T04:38:44Z", "url": "https://github.com/tabdulradi/nullable"}Nullable
Section titled “Nullable”Enriches union types A | Null with an interface similar scala.Option making it usable inside for comprehensions
Add the following to your build.sbt
scalacOptions += "-Yexplicit-nulls"libraryDependencies += "com.abdulradi" %% "nullable-core" % "0.4.0"Features
Section titled “Features”Plays nice with for comprehensions
Section titled “Plays nice with for comprehensions”import com.abdulradi.nullable.syntax.*val maybeA: Int | Null = 42val maybeB: String | Null = "foo"
// Compiles finefor a <- maybeAyield 5
// Also compiles, no null pointer exceptionsfor a <- maybeA b <- nullyield ()
// Compiles, and evaluates to null (because if condition doesn't match)for a <- maybeA if (a < 0)yield aFamiliar Option-like experience
Section titled “Familiar Option-like experience”maybeA.map(_ => 5)maybeA.flatMap(_ => null)maybeA.flatMap(_ => maybeB)maybeA.fold(0)(_ + 1)maybeA.getOrElse(0)maybeA.contains(0)maybeA.exists(_ == 0)maybeA.toRight("Value was null")Convert from/to Option
Section titled “Convert from/to Option”val optA = Some(42)maybeA.toOption == optAmaybeA == optA.orNullPrevents auto flattening
Section titled “Prevents auto flattening”While Option[Option[A]] is a valid type, the equivalent A | Null | Null is indistinguishable from A | Null. So, the library ensures that map and flatMap are used correctly used at compile time.
The following examples don’t compile
for a <- maybeA yield maybeB // Shows a compile time error suggesting to use flatMap insteadmaybeA.flatMap(_ => 5) // Shows a compile time error suggesting message to use map insteadmaybeA.map(_ => null) // Shows a compile time error suggesting to use flatMap insteadmaybeA.map(_ => maybeB) // Shows a compile time error suggesting to use flatMap insteadWorking with Generics
Section titled “Working with Generics”The following doesn’t compile, as we can’t prove A won’t be Null
def useFlatMapWithNullableInScope[A]!(f: Int => A): A | Null = maybeA.flatMap(f)Solution: Propagate a Nullable instance and let the library check at usage site
def useFlatMapWithNullableInScope[A: Nullable]!(f: Int => A): A | Null = maybeA.flatMap(f)
def useMapWithNotNullInScope[A: NotNull]!(f: Int => A): A | Null = maybeA.map(f)Lightweight version
Section titled “Lightweight version”If you only care about for-comprehension features, but not the rest of Option-like methods, we also offer a lightweight syntax
import com.abdulradi.nullable.forSyntax.*
for a <- maybeA b <- maybeB res = a + b if (res % 2) == 0yield resThe rest of the methods like fold, getOrElse, etc won’t be in scope.
License & Acknowledgements
Section titled “License & Acknowledgements”Since this library mimics the scala.Option behavior, it made sense to copy and adapt the documentation of it’s methods and sometimes the implementation too. Those bits are copyrighted by EPFL and Lightbend under Apache License 2.0, which is the same license as this library.