Recently I published NullSafe, a library to provide Kotlin / Groovy flavored null-safe ? operator.
Motivation
Null references are known as The Billion Dollar Mistake. Alternatively, Option is preferred in Scala. Unfortunately, JavaSE API and other Java libraries, which may produce null references, are still used in Scala development. This NullSafe library aims to handle null references in these use cases.
Installation
NullSafe is a part of Dsl.scala project, requiring Dsl.scala’s compiler plug-in:
libraryDependencies += "com.thoughtworks.dsl" %% "keywords-nullsafe" % "latest.release"
addCompilerPlugin("com.thoughtworks.dsl" %% "compilerplugins-bangnotation" % "latest.release")
Usage
You can use @ ? annotation to type a nullable value.
import com.thoughtworks.dsl.keywords.NullSafe._
case class Tree(left: Tree @ ? = null, right: Tree @ ? = null, value: String @ ? = null)
val root: Tree @ ? = Tree(
left = Tree(
left = Tree(value = "left-left"),
right = Tree(value = "left-right")
),
right = Tree(value = "right")
)
A normal . is not null safe, when performing a method on a null value.
a[NullPointerException] should be thrownBy {
root.right.left.right.value
}
The above code throws an exception because root.right.left is null . The exception can be avoided by using ? operator on the nullable value instead:
root.?.right.?.left.?.value should be(null)
The entire expression is null if one of ? is performed on a null value.
The boundary of a null safe operator ? is the nearest enclosing expression whose type is annotated as @ ?.
("Hello " + ("world " + root.?.right.?.left.?.value)) should be("Hello world null")
("Hello " + (("world " + root.?.right.?.left.?.value.?): @ ?)) should be("Hello null")
(("Hello " + ("world " + root.?.right.?.left.?.value.?)): @ ?) should be(null)
Related works
For pure Scala code that produces Options, monads and Monadic !-notation can be used. Monadic is also a library in Dsl.scala project. An example of !-notation with cats’ Option monads can be found in https://scastie.scala-lang.org/Atry/m8NQ38cvR5yKANEGBttyPQ/1