What is the point of the class Option[T]?

10,575

Solution 1

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)

Solution 2

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.

Solution 3

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.

Solution 4

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 fathersMothersSisterto 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.

Solution 5

You have pretty powerful composition capabilities with Option:

def getURL : Option[URL]
def getDefaultURL : Option[URL]


val (host,port) = (getURL orElse getDefaultURL).map( url => (url.getHost,url.getPort) ).getOrElse( throw new IllegalStateException("No URL defined") )
Share:
10,575

Related videos on Youtube

missingfaktor
Author by

missingfaktor

I am no longer active on this site, but if my posts help you and/or you need further help, do let me know on Twitter at @missingfaktor. I will try my best to respond! Note: This profile description was written for StackOverflow, and was copied to all other StackExchange sites as-is.

Updated on May 19, 2020

Comments

  • missingfaktor
    missingfaktor about 4 years

    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?

    • Mitch Blevins
      Mitch Blevins over 14 years
      Actually, the "get" method in Option[T] is pronounced: "Why the hell aren't you pattern matching this?"
    • Daniel C. Sobral
      Daniel C. Sobral over 14 years
      Mitch is right. Try to rephrase your example without using get, and you'll get it. :-)
    • Michael Neale
      Michael Neale over 14 years
      You have Person p .. which is java .. .try val p =... Also, there is more to Option as shown by Daniel and Synesso below - some GREAT answers here.
    • missingfaktor
      missingfaktor over 14 years
      @Michael : Oops! Thanks for pointing; corrected it.
    • Prakash
      Prakash over 13 years
    • Admin
      Admin over 9 years
      It's better because you're explicit about what is and what is not optional. Methods like foreach, map and flatMap are just niceties.
    • missingfaktor
      missingfaktor over 9 years
      @rightføld, it's been five years since I posted the question. I am on the other side now. ;-) But thanks.
    • clay
      clay almost 9 years
      getPerson2.foreach(p => p.display) is simpler than both code examples given. Pattern matching is necessary when you want to do something in the None case.
  • cflewis
    cflewis over 14 years
    This is my understanding of Option too. It explicitly tells the programmer that we could get a None, and if you're foolish enough to remember to do Some(T) but not catch the None as well you're in trouble.
  • missingfaktor
    missingfaktor over 14 years
    In your last example what if the father of person is null? map will return None and the call would fail with some error. How is it better than the null approach?
  • missingfaktor
    missingfaktor over 14 years
    I know it's a monad. Why else would I include a "monad" tag in question?
  • missingfaktor
    missingfaktor over 14 years
    ^ The above statement does not mean that I understand what a monad is. :D
  • paradigmatic
    paradigmatic over 14 years
    No. If person is None (or father, mother or sister), then fathersMothersSister will be None, but no error will be thrown.
  • Michael Neale
    Michael Neale over 14 years
    Lewisham - I think the compiler will give you a warning as Some/None form a algebraic data type (abstract sealed trait...) (but I am going from memory here).
  • paradigmatic
    paradigmatic over 14 years
    Monads are cool. If you don't use them or at least don't pretend to understand then you are not cool ;-)
  • retronym
    retronym over 14 years
    I think you mean flatMap, rather than map.
  • subrat71
    subrat71 over 14 years
    It's a shame that in Scala the <- syntax is confined to "list comprehension syntax", as it's really the same as the more general do syntax from Haskell or the domonad form from Clojure's monad library. Tying it to lists sells it short.
  • Justin Smith
    Justin Smith over 14 years
    The point of the Option type in most languages that use it is that instead of a runtime null exception you get a compile time type error - the compiler can know that you do not have an action for the None condition when using the data, which should be a type error.
  • GClaramunt
    GClaramunt over 14 years
    "For comprehensions" in Scala are essentially the "do" in Haskell, they're not limited to lists, you can use anything that implements: def map[B](f: A => B): C[B] def flatMap[B](f: A => C[B]): C[B] def filter(p: A => Boolean): C[A] . IOW, any monad
  • missingfaktor
    missingfaktor over 14 years
    Thanks a lot for the link. It was really useful. :)
  • Daniel C. Sobral
    Daniel C. Sobral over 14 years
    @seh I upvoted @GClaramunt's comment, but I can't emphasize enough his point. There is no connection between for-comprehensions and lists in Scala -- except for the latter being usable with the former. I refer you to stackoverflow.com/questions/1052476/….
  • subrat71
    subrat71 over 14 years
    Yes, I know that there's no relationship, but I agree that it's worth pointing out; I was commenting on the first line of this answer, where paradigmatic mentions "list comprehension syntax". It's a teaching problem, as opposed to a language design problem.
  • Randall Schulz
    Randall Schulz over 14 years
    I'll add to the recommended reading list Tony Morris' recently published tutorial "What Does Monad Mean?": projects.tmorris.net/public/what-does-monad-mean/artifacts/1‌​.0/…
  • Synesso
    Synesso over 14 years
    Thanks for the edit Daniel. I didn't try the code before posting it. Will do better next time.
  • Justin W
    Justin W over 14 years
    Has anyone done solid benchmarks on the performance hit on a modern VM? Escape analysis means that many temporary Option objects can be allocated on the stack (much cheaper than the heap), and the generational GC handles slightly less temporary objects pretty efficiently. Of course if speed is more important for your project than avoiding NPEs, options are probably not for you.
  • subrat71
    subrat71 almost 14 years
    There's a sad consequence here: there's no jump-based short-circuiting in play, so that every successive statement tests the Option for None again. Had the statements been written as nested conditionals, each potential "failure" would only be tested and acted upon once. In your example, the outcome of fetchRowById is effectively inspected three times: once to guide key's initialization, again for value's, and finally for result's. It's an elegant way to write it, but it's not without its runtime cost.
  • ThanksForYourHelp
    ThanksForYourHelp almost 14 years
    Do not mention performance overhead without numbers to back it up. This is an extremely common mistake when arguing against abstractions like Option. I will happily reverse my downvote if you point to or publish a benchmark or remove the performance comment :)
  • ThanksForYourHelp
    ThanksForYourHelp almost 14 years
    Your comment on the thread was so concise I nearly missed its point. I really wish null could be banned.
  • Kevin Wright
    Kevin Wright almost 14 years
    I think you misunderstand Scala's for-comprehensions. The second example is emphatically NOT a loop, it's translated by the compiler into a series of flatMap operations - as per the first example.
  • fredoverflow
    fredoverflow about 11 years
    "force yourself to never, ever, use get" -> So, in other words: "You don't get it!" :)
  • subrat71
    subrat71 about 11 years
    It's been a long time since I wrote my comment here, but I just saw Kevin's. Kevin, to whom were you referring when you wrote "you misunderstand?" I don't see how it could have been me, as I never mentioned anything about a loop.
  • Jesvin Jose
    Jesvin Jose over 10 years
    Could you explain this completely?
  • Mark Lister
    Mark Lister over 10 years
    val favouriteColour = if (p == null) p.favouriteColour else null //precisely the error that Option helps you avoid! This answer's been here for years without anyone spotting this error!