Mitch Pronschinske is a Senior Content Analyst at DZone. That means he writes and searches for the finest developer content in the land so that you don't have to. He often eats peanut butter and bananas, likes to make his own ringtones, enjoys card and board games, and is married to an underwear model. Mitch is a DZone Zone Leader and has posted 2569 posts at DZone. You can read more from them at their website. View Full User Profile

Free Chapter from "Scala in Depth": Using None instead of Null

09.07.2011
| 8774 views |
  • submit to reddit

Scala in Depth

Joshua 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:

 


Lift in Action

Timothy Perrett


Scala in Action

Nilanjan Raychaudhuri


DSLs in Action

Debasish Ghosh

 

Last updated: August 15, 2011

Tags: