I am not able to understand the point of Option[T]
class in Scala. I mean, I am not able to see any advanages of None
over null
.
For example, consider the code:
object Main{
class Person(name: String, var age: int){
def display = println(name+" "+age)
}
def getPerson1: Person = {
// returns a Person instance or null
}
def getPerson2: Option[Person] = {
// returns either Some[Person] or None
}
def main(argv: Array[String]): Unit = {
val p = getPerson1
if (p!=null) p.display
getPerson2 match{
case Some(person) => person.display
case None => /* Do nothing */
}
}
}
Now suppose, the method getPerson1
returns null
, then the call made to display
on first line of main
is bound to fail with NPE
. Similarly if getPerson2
returns None
, the display
call will again fail with some similar error.
If so, then why does Scala complicate things by introducing a new value wrapper (Option[T]
) instead of following a simple approach used in Java?
UPDATE:
I have edited my code as per @Mitch's suggestion. I am still not able to see any particular advantage of Option[T]
. I have to test for the exceptional null
or None
in both cases. :(
If I have understood correctly from @Michael's reply, is the only advantage of Option[T]
is that it explicitly tells the programmer that this method could return None? Is this the only reason behind this design choice?
Advertisements. Scala Option[ T ] is a container for zero or one element of a given type. An Option[T] can be either Some[T] or None object, which represents a missing value.
The Option in Scala is referred to a carrier of single or no element for a stated type. When a method returns a value which can even be null then Option is utilized i.e, the method defined returns an instance of an Option, in place of returning a single object or a null.
Scala some class returns some value if the object is not null, it is the child class of option. Basically, the option is a data structure which means it can return some value or None. The option has two cases with it, None and Some. We can use this with the collection.
Class Options. java.lang.Object org.apache.commons.cli.Options public class Options extends Object. Main entry-point into the library. Options represents a collection of Option objects, which describe the possible options for a command-line. It may flexibly parse long and short options, with or without values.
You'll get the point of Option
better if you force yourself to never, ever, use get
. That's because get
is the equivalent of "ok, send me back to null-land".
So, take that example of yours. How would you call display
without using get
? Here are some alternatives:
getPerson2 foreach (_.display) for (person <- getPerson2) person.display getPerson2 match { case Some(person) => person.display case _ => } getPerson2.getOrElse(Person("Unknown", 0)).display
None of this alternatives will let you call display
on something that does not exist.
As for why get
exists, Scala doesn't tell you how your code should be written. It may gently prod you, but if you want to fall back to no safety net, it's your choice.
You nailed it here:
is the only advantage of Option[T] is that it explicitly tells the programmer that this method could return None?
Except for the "only". But let me restate that in another way: the main advantage of Option[T]
over T
is type safety. It ensures you won't be sending a T
method to an object that may not exist, as the compiler won't let you.
You said you have to test for nullability in both cases, but if you forget -- or don't know -- you have to check for null, will the compiler tell you? Or will your users?
Of course, because of its interoperability with Java, Scala allows nulls just as Java does. So if you use Java libraries, if you use badly written Scala libraries, or if you use badly written personal Scala libraries, you'll still have to deal with null pointers.
Other two important advantages of Option
I can think of are:
Documentation: a method type signature will tell you whether an object is always returned or not.
Monadic composability.
The latter one takes much longer to fully appreciate, and it's not well suited to simple examples, as it only shows its strength on complex code. So, I'll give an example below, but I'm well aware it will hardly mean anything except for the people who get it already.
for { person <- getUsers email <- person.getEmail // Assuming getEmail returns Option[String] } yield (person, email)
Compare:
val p = getPerson1 // a potentially null Person val favouriteColour = if (p == null) p.favouriteColour else null
with:
val p = getPerson2 // an Option[Person] val favouriteColour = p.map(_.favouriteColour)
The monadic property bind, which appears in Scala as the map function, allows us to chain operations on objects without worrying about whether they are 'null' or not.
Take this simple example a little further. Say we wanted to find all the favourite colours of a list of people.
// list of (potentially null) Persons for (person <- listOfPeople) yield if (person == null) null else person.favouriteColour // list of Options[Person] listOfPeople.map(_.map(_.favouriteColour)) listOfPeople.flatMap(_.map(_.favouriteColour)) // discards all None's
Or perhaps we would like to find the name of a person's father's mother's sister:
// with potential nulls val father = if (person == null) null else person.father val mother = if (father == null) null else father.mother val sister = if (mother == null) null else mother.sister // with options val fathersMothersSister = getPerson2.flatMap(_.father).flatMap(_.mother).flatMap(_.sister)
I hope this sheds some light on how options can make life a little easier.
The difference is subtle. Keep in mind to be truly a function it must return a value - null is not really considered to be a "normal return value" in that sense, more a bottom type/nothing.
But, in a practical sense, when you call a function that optionally returns something, you would do:
getPerson2 match {
case Some(person) => //handle a person
case None => //handle nothing
}
Granted, you can do something similar with null - but this makes the semantics of calling getPerson2
obvious by virtue of the fact it returns Option[Person]
(a nice practical thing, other than relying on someone reading the doc and getting an NPE because they don't read the doc).
I will try and dig up a functional programmer who can give a stricter answer than I can.
For me options are really interesting when handled with for comprehension syntax. Taking synesso preceding example:
// with potential nulls
val father = if (person == null) null else person.father
val mother = if (father == null) null else father.mother
val sister = if (mother == null) null else mother.sister
// with options
val fathersMothersSister = for {
father <- person.father
mother <- father.mother
sister <- mother.sister
} yield sister
If any of the assignation are None
, the fathersMothersSister
will be None
but no NullPointerException
will be raised. You can then safely pass fathersMothersSister
to a function taking Option parameters without worrying. so you don't check for null and you don't care of exceptions. Compare this to the java version presented in synesso example.
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