Thursday, January 07, 2016

Darren on the JVM: Scala: Null Coalescing Operator for Scala

Null (Safe|Dereferencing|Coalescing) Operator

I stumbled on to this concept a while ago when researching the many cool features of Scala. I had come across it before, as I’ll mention, but not so concretely. There is a wealth of information on SO and Wiki, which again leaves mythed why Java (my roots) does not support this.

Anyway, what I offer here - which btw is not new - is slightly different to the normal NCO as found in many other languages, for which it is a supported langauge feature.

Note, NCO is supported implicitly by a wider set of laguages i.e. those where every value or expression has a truth value: any value can be coerced to True or False, after which you defer to logical operators; I found this to be the case in ActionScript (I used to be a Flex developer) and is also the case with Bash, where booleans are applicable.

Having read a few SO posts and seeing what others have put out there, I figured I would have a go at implementing a NCO, seeing what features of Scala can be utilized and how far I can take it.

The aim is to get from this

to this

And just for emphasis, compare the amount of horizontal scrolling you have to do to read those code snippets.

Maybe Null, Maybe Not… It’s Optional

Scala offers Option, Try and Either as ways to program with null and exceptions. Using these aren’t quite the same as programming defensively as the null-check is done automatically under-the-hood. However, like most things Scala, it is a noteworthy paradigm shift… that is, going from Null Programming to Option Programming.

While Option, its various composition functions and pattern matching are cool for handling null, given checking for null is so common in OO (or at least Java), it’s a little surprising that there isn’t seamless support for crossing over from the Null-world to the Option-world in Scala (… presumably seeking to succeed where Java fails).

There is the PartialFunction[A, B].lift which helps, but it only goes one-level deep: the benefit is lost if you are required to use a library with a complex Option-less object model; think of some legacy Java library that does not make use of Options, you may as well stick to defensive programming with null-checks.

Extension Methods a.k.a. Implicits

Thanks to Scala’s extension methods i.e. implicit type conversions, the crossover from null to Option is made very easy.

While we haven’t defined any extension methods explicitly, any function of Option is now available to clients of E.

We could at this point use the familiar composition functions on Option, but we want some nice operator syntactic sugar… it’s all about the sugar!

So taking the idea a step further, we introduce an intermediate wrapper class - a facade if you like - to provide these operators and translate them onto Option compositon functions.

So let’s start again with

What we produce is a very concise way to dereference null and chain a sequence of HOFS together: with this we can drill down into the object graph, evaluating to the targeted value or expression; or to None if null is encountered anywhere along the way.

Here is an excerpt from a ScalaTest spec I wrote while exploring all this:

Here you see an example each for passing functions and passing lambdas (anonymous functions); the latter turns out a little unfortunate with all the programming punctuation.

Nonetheless it works! At this stage we are free to match { ... } or map { ... } and so on.

We can extends NSCOps to include a whole raft of convenience operators.

One very cool addition would be performing value checks, that is, for values other than null

How cool is that; all operators continue down the chain if the option is not None and:

  • ?~ the filter expression evaluates to true
  • ?? the non-None value matches the by-name (lazy) operand
  • ?! the non-None value does not match the by-name (lazy) operand

The by-name operators are probably more useful after the Immutable paradigm shift where case classes are the norm, for which == does the right thing.

Since originally posting, I have made edits to reference the published gist which you can see in it’s entirety here.

PLEASE take note of the non-contiguous line numbers here in the post; the Gist integration I use lets you select line number ranges to show, but does not make it obvious to the reader ellipses would have been nice. Please copy-paste from the gist.

No comments: