Most Java engineers know how to use java.util.Optional
for handling singular null fields. Things get a little more interesting when you have to combine
two (or more) Optionals into a new Optional instance.
Below, we look at how you might do this in both Java and Kotlin. We will use a basic domain object (Person) and how we might construct it using a factory method that takes as arguments the individual nullable properties that make up a Person instance.
We illustrate two ways to create a Person. The first uses an imperative "traditional" style of Java.
This method is unsafe in that isPresent()
checks are separate from get()
calls. In real code, it's possible
that at some point in the future, maybe due to a refactoring, the isPresent()
check gets removed leaving you with
a naked get()
call and possible runtime NPE. This is what makes this approach risky.
The second uses a more declarative functional style. This version is much more concise, and when the mechanism is understood presents a far lower cognitive load. However, it requires an understanding of flatMap()
versus map()
which can take a while to grok for many (most?) engineers. Until that understanding is acquired, it could actually lead to a higher cognitive load. This approach is functionally equivalent to the imperative approach, but with the following advantages:
- No need for separate
isPresent()
andget()
calls. In fact, no need at all for either of those. - We can safely operate on possible null values without ever having to worry about whether or not they are null. This is the real value of using
Optional
. - Much less code
- Much less cognitive load (when the mechanism is understood)
We do a similar thing here here, where we present two approaches. First a vanilla kotlin example, then a more sophisticaed but powerful DSL-driven approach. Even using the vanilla kotlin approach, it's an improvement over Java's functional approach,
though both are similar. In this case, there's no need to reason about whether to use flatMap()
or map()
functions. Instead, we use the let()
scope function, which can be used at all nested levels.
The 2nd example uses a Kotlin specific feature. That being, the ability to create DSLs.
In this example, we are simulating scala's "for comprehensions" (google it). We are essentially creating a "comprehension" that allows us to create a null-safe person creator in a single line, with no nested lambdas, or nesting of any kind.
This approach is functionally equivalent, but a lot easier to use. It just requires a bit of code to setup the DSL, which can be reused anywhere in your project.
This approach to create a "comprehension" DSL can be used for any number of use cases. In this case, we focus on handling nullability.