And You Thought Option is Just Another Way to Handle null


Ok, to start with, the recommendation is to use None instead of null. Ideally if in Scala code, we end up getting a NPE then that is sin! Well at least at Knoldus 🙂

null in Java is used as a placeholder for denoting no value or a non-fatal error situation. It is widely accepted as a Billion dollar mistake of Java. The way to work around it is to have a NullObjectPattern which is present as a pattern in variety of languages and which people seldom use.

Scala is smart. It has an option which is a container. Now the container can either have something or it can be empty. Most of the times developers working in Scala use it in either of the two situations

First, when they are working with a Java API which possibly returns null. Since you would not want to work with null in Scala then you would wrap the Java API call in an Option. Something like this

def someCrappyJavaService = null   //> someCrappyJavaService: => Null
val result = Option(someCrappyJavaService)  //> result  : Option[Null] = None
if (result==None) println("bla")   //> bla

If you would notice, most of the developers coming from the Java world would be writing code like this.

However, Option provides us with lot of cool ways to implement our logic. For starters, instead of checking == you could instead do

def someCrappyJavaService = null     //> someCrappyJavaService: => Null
val result = Option(someCrappyJavaService)//> result  : Option[Null] = None
if (!result.isDefined) println("bla") //> bla

Ok, i see you throwing a stone at me. What is the big deal here? You could also do a getOrElse

def someCrappyJavaService = null
val result = Option(someCrappyJavaService).getOrElse("bla")
println(result)

Still, what is the big deal? Well, we have just started!

One of the cool feature of option is that it can be treated like a collection. What? Yes, you can call methods like map, flatMap, foreach and use it in for comprehensions. Let us see how that makes things easier for us.

for

We want to execute a portion of the logic only if we have got a value in the Option container. There is “a” way and then a better way to do it. See the code below

val result = None //Some("Hello")
def someProcessingLogic(x: String) = 
  println(s"I would execute when there is some value like $x")
//Standard Way
if (result != None) someProcessingLogic(result.get)
// Better Way!
for (res <- result) someProcessingLogic(res)

Now the for comprehension would evaluate only if res is not an empty box i.e. it has some value. But, what if there are multiple options that we want to compose together. For example the following code block

  val option1 = Some(10)
  val option2 = None
  val option3 = Some("Hi")
  for {
    a <- option1
    b <- option2
    c <- option3
  } yield a + b + c

What do you think is going to be the output of this code block? If you answered None you are correct. And now?

  val option1 = Some(10)
  val option2 = Some(99)
  val option3 = Some("Hi")
  for {
    a <- option1
    b <- option2
    c <- option3
  } yield a + b + c

The result in this case would be Some(109Hi)

map
Let us look at a good case of using a map now. Look at the code below

val a:Option[Int]= Some(1)  //> a  : Some[Int] = Some(1)
val c = a.get + 1           //> c  : Int = 2

What would happen to this code if a was None. It would throw a java.util.NoSuchElementException: None.get.

Another way of working with this code is to map over it like a collection.

  val a: Option[Int] = None //> a  : Option[Int] = None
  val b = a map (_ + 1)     //> b  : Option[Int] = None
  val a: Option[Int] = Some(1)  //> a  : Option[Int] = Some(1)
  val b = a map (_ + 1)         //> b  : Option[Int] = Some(2)

map would result in a value since it would be evaluated as an expression. If you would like to have normal looping for a side effect operation, you could use a foreach instead. Example

 val result: Option[String] = Some("Hello") //> result  : Option[String] = Some(Hello)
 def someProcessingLogic(x: String) = println(s"I would execute with value like $x")
 result map { x => someProcessingLogic(x) } //> I would execute with value like Hello
                                            //| res1: Option[Unit] = Some(())
 result foreach { x => someProcessingLogic(x) }  //> I would execute with value like Hello

similarly you can flatMap on an option.

filter

Just like collections, it is easy to filter on an option if it has a value

val result: Option[String] = Some("Hello") //> result  : Option[String] = Some(Hello)
result filter (_.size > 3) foreach (x => println(x)) //> Hello
val noneResult: Option[String] = None //> noneResult  : Option[String] = None
noneResult filter (_.size > 3) foreach (x => println(x))

As you would expect by now, the empty box does not get evaluated.

chaining

We can chain several options together just like PartialFunction with orElse

None orElse Some(10) orElse None orElse Some(1) //> res5: Option[Int] = Some(10)

more

Then there are other things to try like

val result: Option[String] = Some("Hello") //> result  : Option[String] = Some(Hello)
result.toList        //> res2: List[String] = List(Hello)
None.isEmpty         //> res3: Boolean = true

Thus as you would have noticed that Option is quite powerful. It is more than just checking for null or None and writing your business logic around it. Option is a monad in the loose sense since it supports composition by flatMap. It has the ability to be used in for comprehension and provides collection functions which makes it quite useful to write well meaning code.

The worksheet gist for all the above code is present here. Have fun!

Advertisements

About Vikas Hazrati

Vikas is the Founding Partner @ Knoldus which is a group of software industry veterans who have joined hands to add value to the art of software development. Knoldus does niche Reactive and Big Data product development on Scala, Spark and Functional Java. Knoldus has a strong focus on software craftsmanship which ensures high-quality software development. It partners with the best in the industry like Lightbend (Scala Ecosystem), Databricks (Spark Ecosystem), Confluent (Kafka) and Datastax (Cassandra). To know more, send a mail to hello@knoldus.com or visit www.knoldus.com
This entry was posted in Scala and tagged , . Bookmark the permalink.

2 Responses to And You Thought Option is Just Another Way to Handle null

  1. Satendra suggests 2 more things

    1) What if you have a scenario like if then SOMETHING else SOMETHING_ELSE . The way to deal that with an option would be like
    val b = Some(1)
    b map (x=>x+1) getOrElse(9)
    This is ok, but the problem with getOrElse is that it would not complain about the datatype. So if instead of the above, we had
    val b = Some(1)
    b map (x=>x+1) getOrElse(“9”)

    That would continue to work.

    A better way is to use fold instead
    b.fold(1)(x=>x+1) where (1) is the else state and (x=>x+1) is the function to be called when we get a Some. This would complain when we change the datatype
    b.fold(“1”)(x=>x+1) // This would not compile

  2. Pingback: Working Effortlessly with Java null in Scala | Knoldus

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s