Null-safety Part 2: Working With Null in Scala
So how then, given this problem, are we to try to write null-safe code in Scala? NullPointerExceptions (NPEs) occur when we try to access a field, method, or index, of an object that is actually null
.
Most Scala programmers would opt to wrap their code in Option
in order to avoid NPEs, however sometimes you don’t have the option (pun intended) to do this. I frequently ran into this situation at my work, where we needed to pull data out of highly nested Avro classes, in some cases 6 levels deep. Avro schemas get compiled into regular Java objects, that don’t use Option
. So then what, if your code is not designed around Option
, is the best approach to extract deeply nested values in a null-safe way? Essentially, what is missing from Scala here is a safe navigation operator similar to Groovy or Kotlin.§
In order to find the best solution to this problem, I began by enumerating all of the possible ways that I could think of to implement null-safe access.
1. Explicit null safety
Pros: Very efficient, just comprised of null-checks
Cons: Poor readability and writability
2. Option flatmap
Pros: Better read/writability, but still not great.
Cons: High performance overhead, object allocation + virtual method calls per level of drilldown
3. For Loop
Similar to the approach #2, but slightly slower and worse readability.
4. Null-safe navigator extension method
Pros: Pretty readable syntax. No object allocation.
Cons: Syntax still not perfect. 1 function call per level of drilldown
5. Try Catch NPE
Pros: Syntax could be very nice if abstracted out to a function
Cons: Harsh performance penalty in case of NPE. Could intercept other NPEs
6. Monocle Lenses
I didn’t really consider using lenses when I began this project, but someone asked me one time, “Why not use lenses, like in Monocle?” So I tried it out, but it didn’t succeed in either read/writability, or performance, in this use case.
7. § com.thoughtworks NullSafe DSL
So actually, when I began this project I didn’t know that this library existed. Essentially what it does is add in the missing safe navigation operator to Scala, via a compiler plugin.
It has nice syntax, but it does introduce some performance overhead.
Comparing Approaches
Here are some benchmarks of the different approaches.
Data in tabular form
In order to summarize the pros and cons of each approach, let’s evaluate them based on null-safty, read/writability, and efficiency.
Null-safe | Readable / Writable | Efficient | |
---|---|---|---|
Normal access | ️ | ️ | |
Explicit null-checks | ️ | ️ | |
Option flatMap | ️ | ||
For loop flatMap | ️ | ️ | |
Null-safe navigator | ️ | ️ | ️ |
Try-catch NPE | ️ | ️ | ️ |
Monocle Optional (lenses) | ️ | ||
thoughtworks NullSafe DSL | ️ | ️ | ️ |
Key: ️ = Good, = Sub-optimal, = Bad
After evaluating all of the options available, I wasn’t quite satisfied with any of them, so I decided to create a new way via Scala’s blackbox macros.