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?
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.
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.
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.
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.
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 null
s. 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 null
s, 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 null
s, and hence null
-checking simply becomes redundant.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With