views:

1342

answers:

16
+33  Q: 

Why Option[T] ?

Hi everyone,

I am a beginner to functional programming and I have recently started studying Scala and I really love this language for the all goodies it provides like closures, pattern matching, currying etc.

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

e.g. 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?

Any help would be greatly appreciated.

Thanks!

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?

Please shed some more light.

Thanks!

+18  A: 

The difference is subtle. Keep in mind to be truly a function is 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 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 then 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 then I can !

** http://en.wikipedia.org/wiki/Bottom_type

Michael Neale
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.
Lewisham
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).
Michael Neale
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.
Justin Smith
A: 

Null return values are only present for comatibility with java. You should not use them otherwise.

ryeguy
A: 

Admitting in advance that it is a glib answer, Option is a monad.

Randall Schulz
I know it's a monad. Why else would I include a "monad" tag in question?
missingfaktor
^ The above statement does not mean that I understand what a monad is. :D
missingfaktor
Monads are cool. If you don't use them or at least don't pretend to understand then you are not cool ;-)
paradigmatic
+2  A: 

Adding on to Randall's teaser of an answer, understanding why the potential absence of a value is represented by Option requires understanding what Option shares with many other types in Scala -- specifically, types modeling monads. If one represents the absence of a value with null, that absence-presence distinction can't participate in the contracts shared by the other monadic types.

If you don't know what monads are, or if you don't notice how they're represented in Scala's library, you won't see what Option plays along with, and you can't see what you're missing out on. There are many benefits to using Option instead of null that would be noteworthy even in the absence of any monad concept (I discuss some of them in the "Cost of Option / Some vs null" scala-user mailing list thread here), but talking about it isolation is kind of like talking about a particular linked list implementation's iterator type, wondering why it's necessary, all the while missing out on the more general container/iterator/algorithm interface. There's a broader interface at work here too, and Option provides a presence-and-absence model of that interface.

seh
Thanks a lot for the link. It was really useful. :)
missingfaktor
Your comment on the thread was so concise I nearly missed its point. I really wish null could be banned.
Alain O'Dea
+17  A: 

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.

Synesso
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
No. If person is None (or father, mother or sister), then fathersMothersSister will be None, but no error will be thrown.
paradigmatic
I think you mean flatMap, rather than map.
retronym
Thanks for the edit Daniel. I didn't try the code before posting it. Will do better next time.
Synesso
You're welcome.
Daniel
+13  A: 

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.

EDIT

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)
Daniel
+8  A: 

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.

paradigmatic
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.
seh
"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
GClaramunt
@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 http://stackoverflow.com/questions/1052476/can-someone-explain-scalas-yield.
Daniel
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.
seh
@seh you are right. I have edited my post to clarify.
paradigmatic
+3  A: 

It's not there to help avoid a null check, it's there to force a null check. The point becomes clear when your class has 10 fields, two of which could be null. And your system has 50 other similar classes. In the Java world, you try to prevent NPEs on those fields using some combination of mental horesepower, naming convention, or maybe even annotations. And every Java dev fails at this to a significant degree. The Option class not only makes "nullable" values visually clear to any developers trying to understand the code, but allows the compiler to enforce this previously unspoken contract.

Adam Rabung
+1  A: 

I think the key is found in Synesso's answer: Option is not primarily useful as a cumbersome alias for null, but as a full-fledged object that can then help you out with your logic.

The problem with null is that it is the lack of an object. It has no methods that might help you deal with it (though as a language designer you can add increasingly long lists of features to your language that emulate an object if you really feel like it).

One thing Option can do, as you've demonstrated, is to emulate null; you then have to test for the extraordinary value "None" instead of the extraordinary value "null". If you forget, in either case, bad things will happen. Option does make it less likely to happen by accident, since you have to type "get" (which should remind you that it might be null, er, I mean None), but this is a small benefit in exchange for an extra wrapper object.

Where Option really starts to show its power is helping you deal with the concept of I-wanted-something-but-I-don't-actually-have-one.

Let's consider some things you might want to do with things that might be null.

Maybe you want to set a default value if you have a null. Let's compare Java and Scala:

String s = (input==null) ? "(undefined)" : input;
val s = input getOrElse "(undefined)"

In place of a somewhat cumbersome ?: construct we have a method that deals with the idea of "use a default value if I'm null". This cleans up your code a little bit.

Maybe you want to create a new object only if you have a real value. Compare:

File f = (filename==null) ? null : new File(filename);
val f = filename map (new File(_))

Scala is slightly shorter and again avoids sources of error. Then consider the cumulative benefit when you need to chain things together as shown in the examples by Synesso, Daniel, and paradigmatic.

It isn't a vast improvement, but if you add everything up, it's well worth it everywhere save very high-performance code (where you want to avoid even the tiny overhead of creating the Some(x) wrapper object).

The match usage isn't really that helpful on its own except as a device to alert you about the null/None case. When it is really helpful is when you start chaining it, e.g., if you have a list of options:

val a = List(Some("Hi"),None,Some("Bye"));
a match {
  case List(Some(x),_*) => println("We started with " + x)
  case _ => println("Nothing to start with.")
}

Now you get to fold the None cases and the List-is-empty cases all together in one handy statement that pulls out exactly the value you want.

Rex Kerr
+1  A: 

Actually I share the doubt with you. About Option it really bothers me that 1) there is a performance overhead, as there is a lor of "Some" wrappers created everywehre. 2) I have to use a lot of Some and Option in my code.

So to see advantages and disadvantages of this language design decision we should take into consideration alternatives. As Java just ignores the problem of nullability, it's not an alternative. The actual alternative provides Fantom programming language. There are nullable and non-nullable types there and ?. ?: operators instead of Scala's map/flatMap/getOrElse. I see the following bullets in the comparison:

Option's advantage:

  1. simpler language - no additional language constructs required
  2. uniform with other monadic types

Nullable's advantage:

  1. shorter syntax in typical cases
  2. better performance (as you don't need to create new Option objects and lambdas for map, flatMap)

So there is no obvious winner here. And one more note. There is no principal syntactic advantage for using Option. You can define something like:

def nullableMap[T](value: T, f: T => T) = if (value == null) null else f(value)

Or use some implicit conversions to get pritty syntax with dots.

Alexey
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.
Justin W
@Alexey: 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 :)
Alain O'Dea
+3  A: 

Option[T] is a moand, which is really useful when you using high-order functions to mainpluate values.

I'll suggest you read articles listed below, they are really good articles shows you why Option[T] is useful and how can it be used in functional way.

Brian Hsu
I'll add to the recommended reading list Tony Morris' recently published tutorial "What Does Monad Mean?": http://projects.tmorris.net/public/what-does-monad-mean/artifacts/1.0/chunk-html/index.html
Randall Schulz
+5  A: 

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") )
Viktor Klang
+2  A: 

[ copied from this comment by Daniel Spiewak ]

If the only way to use Option were to pattern match in order to get values out, then yes, I agree that it doesn’t improve at all over null. However, you’re missing a *huge* class of its functionality. The only compelling reason to use Option is if you’re using its higher-order utility functions. Effectively, you need to be using its monadic nature. For example (assuming a certain amount of API trimming):

val row: Option[Row] = database fetchRowById 42
val key: Option[String] = row flatMap { _ get “port_key” }
val value: Option[MyType] = key flatMap (myMap get)
val result: MyType = value getOrElse defaultValue

There, wasn’t that nifty? We can actually do a lot better if we use for-comprehensions:

val value = for {
row <- database fetchRowById 42
key <- row get "port_key"
value <- myMap get key
} yield value
val result = value getOrElse defaultValue

You’ll notice that we are *never* checking explicitly for null, None or any of its ilk. The whole point of Option is to avoid any of that checking. You just string computations along and move down the line until you *really* need to get a value out. At that point, you can decide whether or not you want to do explicit checking (which you should never have to do), provide a default value, throw an exception, etc.

I never, ever do any explicit matching against Option, and I know a lot of other Scala developers who are in the same boat. David Pollak mentioned to me just the other day that he uses such explicit matching on Option (or Box, in the case of Lift) as a sign that the developer who wrote the code doesn’t fully understand the language and its standard library.

I don’t mean to be a troll hammer, but you really need to look at how language features are *actually* used in practice before you bash them as useless. I absolutely agree that Option is quite uncompelling as *you* used it, but you’re not using it the way it was designed.

missingfaktor
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.
seh
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.
Kevin Wright
+1  A: 

It is really a programming style question. Using Functional Java, or by writing your own helper methods, you could have your Option functionality but not abandon the Java language:

http://functionaljava.org/examples/#Option.bind

Just because Scala includes it by default doesn't make it special. Most aspects of functional languages are available in that library and it can coexist nicely with other Java code. Just as you can choose to program Scala with nulls you can choose to program Java without them.

spullara
+3  A: 

Maybe someone else pointed this out, but I didn't see it:

One advantage of pattern-matching with Option[T] vs. null checking is that Option is a sealed class, so the Scala compiler will issue a warning if you neglect to code either the Some or the None case. There is a compiler flag to the compiler that will turn warnings into errors. So it's possible to prevent the failure to handle the "doesn't exist" case at compile time rather than at runtime. This is an enormous advantage over the use of the null value.

Paul Snively
+2  A: 

http://ocaml.janestreet.com/?q=node/78

vlabrecque