Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best practice for null-checking in Scala

Tags:

scala

I understand that null is frowned upon in Scala, and that one should always wrap optional values within an Option (or a "null type" if one is available).

The advantages are clear, there should never be a need to check against null values, and the resulting code is both safer and more pleasant to read.

In practice however, nothing prevents a value from being null - the rule is not enforced by the compiler and is more of a gentleman's agreement. This can easily break when interacting with Java APIs, for example.

Is there a best-practice for this? Say, for example, that I need to write an Url case class, and that instances of this class can be created from a java.net.URI:

object Url {
  def apply(uri: URI): Url = Url(uri.getScheme, uri.getHost, uri.getRawPath)
}

case class Url(protocol: String, host: String, path: String) {
  def protocol(value: String): Url = copy(protocol = value)
  def host(value: String): Url = copy(host = value)
  def path(value: String): Url = copy(path = value)
}

An URI instance can return null for getScheme, getHost and getRawPath. Is it considered sufficient to protect against these in the apply(URI) method (with require statements, for example)? Technically, in order to be entirely safe, it'd be better to protect the protocol, host and path helper methods, as well as the constructor, but that sounds like an awful lot of work and boilerplate.

Is it considered safe, instead, to protect against APIs known to accept / return null values (URI in our example) and to assume that external callers will either not pass null values or only have themselves to blame if they do?

like image 949
Nicolas Rinaudo Avatar asked Jun 02 '14 10:06

Nicolas Rinaudo


People also ask

Is it good practice to use null in Scala?

Don't return null from methods Because you should never use null in your code, the rule for returning null values from methods is easy: don't do it.

How do you handle null values in Scala?

In Scala, using null to represent nullable or missing values is an anti-pattern: use the type Option instead. The type Option ensures that you deal with both the presence and the absence of an element. Thanks to the Option type, you can make your system safer by avoiding nasty NullPointerException s at runtime.

Is there null in Scala?

class Null extends AnyRef Nothing - at the bottom of the Scala type hierarchy. Null is the type of the null literal. It is a subtype of every type except those of value classes. Value classes are subclasses of AnyVal, which includes primitive types such as Int, Boolean, and user-defined value classes.

How do you check if data is null?

You can check for null with the typeof() operator in JavaScript. Curiously, if you check with typeof() , a null variable will return object . This is because of a historic bug in JavaScript.


1 Answers

When dealing with pure functions, it is always better to stick to total implementations and instead of using null, require or exception throwing, to encode all failures in data. The types like Option and Either are there exactly for that. Both of them are monads, so you can use the "for"-syntax to with them.

The following modification to your code shows a total function apply, which only produces a meaningful value, when the input Java URI provides no nulls. We encode the notion of "meaningful" by wrapping the result in Option.

object Url {
  def apply(uri: java.net.URI): Option[Url] =
    for {
      protocol <- Option(uri.getScheme)
      host <- Option(uri.getHost)
      path <- Option(uri.getRawPath)
    }
    yield Url(protocol, host, path)
}

case class Url(protocol: String, host: String, path: String)

The function Option is a standard null-checking function, which maps null to None and wraps values in Some.


Concerning your "setting" functions, you can implement null-checking in them similarly, using Option. E.g.:

case class Url(protocol: String, host: String, path: String) {
  def protocol(value: String): Option[Url] = 
    Option(value).map(v => copy(protocol = v))
  // so on...
}

However I would advice against that. In Scala it is conventional to never use nulls, so it is as well conventional to only implement null-handling for "bridge" APIs, when you get values from Java libs. That's why the null-checking does make perfect sense when converting from java.net.URI, but after that you are in Scala world, where there should be no nulls, and hence null-checking simply becomes redundant.

like image 100
Nikita Volkov Avatar answered Sep 28 '22 10:09

Nikita Volkov