The Known Unknown: Working with Option and Nullable Types

and a set of operators to work with these types, like the safe call operator, the null coalescing operator or the famous elvis operator.

Both approaches provide a strict distinction between nullable and non-nullable types and solve the same kind of problems:Expressing the non-existence of values in the type system that forces the developer to actually handle non-existing values before using them.

While a flat value with type T can be used directly, values with type Option[T] cannot and need to be handled (that means “flattened”) first.

The same goes for values with type T?, which cannot be assigned to variables of type T without safely dereferencing the value (e.

g.

by providing a default value).

A common use case for languages with option or nullable types is returning them for get calls on maps:// Scaladef get(key: K): Option[V]// Haskelllookup :: Ord k => k -> Map k a -> Maybe a// Kotlinoperator fun get(key: K): V?Scala and Haskell return an instance of Option.

Kotlin on the other hand uses a nullable type indicating that the value may not exist.

Sadly, even though Java has the Optional type, get on Java maps still return the value directly.

As usual, the reason for this decision is backwards compatibility.

Working with OptionOption types were not designed with exactly the null reference in mind.

Option is a much broader concept for generalizing any kind of value that evaluates to some empty value.

May it be the non-existing object value “null”, some custom empty integer value “-1” or a custom boolean flag indicating that the flag has never been set to any value.

Option makes chained computations very easy, each possibly returning an option value itself, which can then be flattened and composed together.

Commonly, the Option type is defined as a sum type (also called tagged union) consisting of the two distinctive representations “value” and “no value”.

The following definition is taken from the scala.

Option type:sealed abstract class Option[+A] extends Product with Serializablefinal case class Some[+A](value: A) extends Option[A]case object None extends Option[Nothing]There is either some value a or no value at all.

With Option being a container type, the value inside an Option cannot be used directly.

So, for example in Scala you have multiple ways to extract the value from the option type.

Direct extraction:// getOrElse (e.

g.

for default values)val host = configuration .

parseProperty("host") .

getOrElse("localhost")// orElse (e.

g.

for fail-fast)val user = userRepository .

findById(userId) .

orElse(throw new UserNotFoundException(s"User with id ${userId} does not exist!"))Structural extraction:val candidate = new URI(path)val url = Option(candidate.

getScheme) match { case None => new File(path).

toURI.

toURL case Some(_) => candidate.

toURL}Using pattern matching is especially useful here as it emphasizes the branches (having some value or having no value) very clearly.

Avoid pattern like the following:val maybeUser = userRepository.

findById(userId)if (maybeUser.

isDefined) { val user = maybeUser.

get sendNewsletter(user)}Brian Goetz regarding Java Optional.

get:“There is a get() method on Optional; we should have never called it get().

We should have called it getOrThrowSomethingHorribleIfTheThingIsEmpty() because everybody calls it thinking, ‘I am just supposed to call Optional.

get()’ and they don’t realize that it completely undermines the purpose of using Optional, because it is going to throw if the Optional is empty.

”(JAX 2015 Fragen und Antworten zu Java 8 with Angelika Langer (~16:00), https://jaxenter.

de/fragen-und-antworten-zu-java-8-qa-33108)Extracting the value from the Option should not be your first intention.

The Scala Standard Library states the following:The most idiomatic way to use a scala.

Option instance is to treat it as a collection or monad and use map,flatMap, filter, or foreach.

The Option type is designed for mapping and composing operations together that may or may not produce values without having to fear that the encapsulated value may not be present:val twitterMessage = userRepository .

findUserByEmail("varian.

wrynn@azeroth.

de") .

filter(_.

active) .

flatMap(twitterRepository.

findMostRecentPostByUser) .

map(_.

message)Some languages provide a short-hand syntax for these kind of operations namely the for-comprehension:val twitterMessage = for { user <- userRepository.

findUserByEmail("varian.

wrynn@azeroth.

de") if user.

active recentPost <- twitterRepository.

findMostRecentPostByUser(user)} yield recentPost.

messageThe two code examples are doing exactly the same.

In fact, Scala for example will compile the for-comprehension to chained calls of map, filter and flatMap.

Working with nullable typesNullable types on the other hand were designed to handle null values with the least amount of boilerplate as possible.

The focus lies on traversing through object graphs, where any field access may potentially lead to a NPE being thrown.

Nullable types can be accessed by using the safe call (or safe navigation) operator ?.

and transformed by using the null-coalescing operator or elvis operator ?: (sometimes named interchangeably):val user = userRepository.

findById(userId)// trying to reference a field directlyuser.

lastName> error: only safe (?.

) or non-null asserted (!!.

) calls are allowed on a nullable receiver of type User?// using safe navigation operator insteaduser?.

lastName> null// providing a default value in case of nulluser?.

lastName ?: "Wrynn"> Wrynn// more reasonably throw a custom exceptionthrow UserNotFoundException("No user with id ${userId} found in repository!")While Option types are thought to be used as return values only, nullable types on the other hand may also be used in fields.

// safely referencing, which returns a value or nullperson?.

address?.

citySimilar to Option.

get there exists a not-null assertion operator for nullable types that will obviously throw a NPE when the value is null and should therefore be used carefully.

Depending on the language the operator is !.(TypeScript) or !!.(Kotlin).

// unsafe referencing, which returns a value or a NPEperson!!.

address!!.

city> kotlin.

KotlinNullPointerExceptionOption or nullable type?There’s always a wild discussion between the followers of the two approaches about which concept is better, more concise or more functional and which not.

Option generalizes over non-existing values of any kind, not only null references.

It was defined for chaining operations together, each operation possibly returning an empty value.

The focus of nullable types is making null-handling as comfortable as possible for the developer without much additional code to write.

Both concepts force the developer to check whether the value exists or not.

For me personally, the Option type feels more comfortable to work with.

Although it is more verbose to handle, it provides beneficial functionality for chaining operations and flattening nested values.

Which of the two approaches do you prefer?Thanks for reading!.Feel free to comment or message me, when you have questions or suggestions.

.

. More details

Leave a Reply