Free Chapter from "Scala in Depth": Using None instead of Null
Join the DZone community and get the full member experience.
Join For FreeJoshua D. Suereth
An Option can be considered a container of something or nothing. This is done through the two subclasses of Option: Some and None. In this article from chapter 2 of Scala in Depth, author Joshua Suereth discusses advanced Option techniques.
Using None instead of Null
Scala does its best to discourage the use of null in general programming. It does this through the scala.Option class found in the standard library. An Option can be considered a container of something or nothing. This is done through the two subclasses of Option: Some and None. Some denotes a container of exactly one item. None denotes an empty container, a role similar to what Nil plays for List.
In Java and other languages that allow null, null is often used as a placeholder to denote a nonfatal error as a return value or to denote that a variable is not yet initialized. In Scala, you can denote this through the None subclass of Option. Conversely, you can denote an initialized or nonfatal variable state through the Some subclass of Option. Let’s take a look at the usage of these two classes.
Listing 1 Simple usage of Some and None
scala> var x : Option[String] = None #1
x: Option[String] = None
scala> x.get #2
java.util.NoSuchElementException: None.get in
scala> x.getOrElse("default") #3
res0: String = default
scala> x = Some("Now Initialized") #4
x: Option[String] = Some(Now Initialized)
scala> x.get #5
res0: java.lang.String = Now Initialized
scala> x.getOrElse("default") #6
res1: java.lang.String = Now Initialized
#1 Create uninitialized String variable
#2 Access uninitialized throws exception
#3 Access using default
#4 Initialize x with a string
#5 Access initialized variable works
#6 Default is not used
An Option containing no value can be constructed via the None object. An Option that contains a value is created via the Some factory method. Option provides many ways of retrieving values from its inside. Of particular use are the get and getOrElse methods. The get method will attempt to access the value stored in an Option and throw an exception if it is empty. This is very similar to accessing nullable values within other languages. The getOrElse method will attempt to access the value stored in an Option, if one exists, otherwise it will return the value supplied to the method. You should always prefer getOrElse over using get.
Scala provides a factory method on the Object companion object that will convert from a Java style reference—where null implies an empty variable—into an Option where this is more explicit. Let’s take a quick look.
Listing 2 Usage of the Option factory
scala> var x : Option[String] = Option(null) x: Option[String] = None scala> x = Option("Initialized") x: Option[String] = Some(Initialized)
The Option factory method will take a variable and create a None object if the input was null, or a Some if the input was initialized. This makes it rather easy to take inputs from an untrusted source, such as another JVM language, and wrap them into Options. You might be asking yourself why you would want to do this. Isn’t checking for null just as simple in code? Well, Option provides a few more advanced features that make it far more ideal then simply using if null checks.
Advanced Option techniques
The greatest feature of Option is that you can treat it as a Collection. This means you can perform the standard map, flat Map, and foreach methods and utilize them inside a for expression. Not only does this help to ensure a concise syntax, but opens up a variety of different methods for handling uninitialized values.
Let’s take a look at some common Null-related issues solved using Option, starting with creating an object or returning a default.
Creating a new object or returning a default
Many times, you need to construct something with some other variable or supply some sort of default. Let’s pretend that we have an application that requires some kind of temporary file storage for its execution. The application is designed so that a user may be able to specify a directory to store temporary files on the command line. If the user does not specify a new file, if the argument provided by the user is not a real directory, or they did not provide a directory, then we want to return a sensible default temporary directory. Let’s create a method that will give our temporary directory.
Listing 3 Creating an object or returning a default
def getTemporaryDirectory(tmpArg : Option[String]) : java.io.File = { tmpArg.map(name => new java.io.File(name)). filter(_.isDirectory). getOrElse(new java.io.File(System.getProperty("java.io.tmpdir"))) }
#1 Create if defined
#2 Only directories
#3 Specify Default
The getTemporaryDirectory method takes the command line parameter as an Option containing a String and returns a File object referencing the temporary directory we should use. The first thing we do is use the map method on Option to create a java.io.File if there was a parameter. Next, we make sure that this newly constructed file object is a directory. To do that, we use the filter method. This will check whether the value in an Option abides by some predicate and, if not, convert to a None. Finally, we check to see if we have a value in the Option; otherwise, we return the default temporary directory.
This enables a very powerful set of checks without resorting to nested if statements or blocks. There are times where we would like a block, such as when we want to execute a block of code based on the availability of a particular parameter.
Executing block of code if variable is initialized
Option can be used to execute a block of code if the Option contains a value. This is done through the for each method, which, as expected, iterates over all the elements in the Option. As an Option can only contain zero or one value, this means the block either executes or is ignored. This syntax works particularly well with for expressions. Let’s take a quick look.
Listing 4 Executing code if option is defined
val username : Option[String] = ... for(uname <- username) { println("User: " + uname) }
As you can see, this looks like a normal "iterate over a collection" control block. The syntax remains quite similar when we need to iterate over several variables. Let’s look at the case where we have some kind of Java Servlet framework and we want to be able to authenticate users. If authentication is possible, we want to inject our security token into the HttpSession so that later filters and servlets can check access privileges for this user.
Listing 5 Executing code if several options are defined
def authenticateSession(session : HttpSession, username : Option[String], password : Option[Array[Char]]) = { for(u <- username; p <- password; if canAuthenticate(username, password)) { #1 val privileges = privilegesFor(u) #2 injectPrivilegesIntoSession(session, privileges) } }
#1 Conditional logic
#2 No need for Option
Notice that you can embed conditional logic in a for expression. This helps keep less nested logical blocks within your program. Another important consideration is that all the helper methods do not need to use the Option class.
Option works as a great front-line defense for potentially uninitialized variables; however, it does not need to pollute the rest of your code. In Scala, Option as an argument implies that something may not be initialized—its convention to make the opposite true, that is: functions should not be passed as null or uninitialized parameters.
Scala’s for expression syntax is rather robust, even allowing you to produce values rather than execute code blocks. This is especially handy when you have a set of potentially uninitialized parameters that you want to transform into something else.
Using several potential uninitialized variables to construct another variable
Sometimes we want to transform a set of potentially uninitialized values so that we only have to deal with one. To do this, we’re going to use a for expression again, but this time using a yield. Let’s look at the case where a user has input some database credentials or we attempted to read them from an encrypted location and we want to create a database connection using these parameters. We don’t want to deal with failure in our function because this is a utility function that will not have access to the user. In this case, we’d like to just transform our database connection configuration parameters into a single option containing our database.
Listing 6 Merging options
def createConnection(conn_url : Option[String], conn_user : Option[String], conn_pw : Option[String]) : Option[Connection] = for { url <- conn_url user <- conn_user pw <- conn_pw } yield DriverManager.getConnection(url, user, pw)
This function does exactly what we need it to. It does seem though that we are merely deferring all logic to DriverManager.getConnection. What if we wanted to abstract this such that we can take any function and create one that is option friendly in the same manner? Take a look at what we’ll call the lift function:
Listing 7 Generically converting functions
scala> def lift3[A,B,C,D](f : Function3[A,B,C,D]) : Function3[Option[A], Option[B], Option[C], Option[D]] = | (oa : Option[A], ob : Option[B], oc : Option[C]) => | for(a <- oa; b <- ob; c <- oc) yield f(a,b,c) | } lift3: [A,B,C,D](f: (A, B, C) => D)(Option[A], Option[B], Option[C]) => Option[D] scala> lift3(DriverManager.getConnection) #1 res4: (Option[java.lang.String], Option[java.lang.String], Option[java.lang.String])
#1 Using lift3 directly
The lift 3 method looks somewhat like our earlier createConnection method, except that it takes a function as its sole parameter. As you can see from the REPL output, we can use this against existing functions to create option-friendly functions. We’ve directly taken the DriverManager.getConnection method and lifted it into something that is semantically equivalent to our earlier createConnection method. This technique works well when used with the encapsulation of uninitialized variables. You can write most of your code, even utility methods, assuming that everything is initialized, and then lift these functions into Option friendly as appropriate.
Summary
Scala provides a class called Option that allows developers to relax the amount of protection they need when dealing with null. Option can help to improve reasonability of the code by clearly delineated where uninitialized values are accepted.
Here are some other Manning titles you might be interested in:
Timothy Perrett |
|
Nilanjan Raychaudhuri |
|
Debasish Ghosh
|
Last updated: August 15, 2011
Opinions expressed by DZone contributors are their own.
Comments