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 Option
s, 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