43

I am curious:

scala> Some(null) == None
res10: Boolean = false

Why isn't Some(null) transformed to None?

Xavier Guihot
  • 54,987
  • 21
  • 291
  • 190
Geo
  • 93,257
  • 117
  • 344
  • 520

6 Answers6

65

You should use Option(null) to reach the desired effect and return None.

Some(null) just creates a new Option with a defined value (hence Some) which is actually null, and there are few valid reasons to ever create one like this in real code.

Jacek Laskowski
  • 72,696
  • 27
  • 242
  • 420
Jean-Philippe Pellet
  • 59,296
  • 21
  • 173
  • 234
33

Unfortunately, null is a valid value for any AnyRef type -- a consequence of Scala's interoperability with Java. So a method that takes an object of type A and, internally, store it inside an Option, might well need to store a null inside that option.

For example, let's say you have a method that takes the head of a list, checks if that head correspond to a key in a store, and then return true if it is. One might implement it like this:

def isFirstAcceptable(list: List[String], keys: Set[String]): Boolean =
    list.headOption map keys getOrElse false

So, here's the thing... if the that inside list and keys come from some Java API, they both may well contain null! If Some(null) wasn't possible, then isFirstAcceptable(List[String](null), Set[String](null)) would return false instead of true.

Daniel C. Sobral
  • 295,120
  • 86
  • 501
  • 681
  • 2
    @Jean-PhilippePellet Arguably `null` shouldn't exist at all in Scala, and every Java-to-Scala interface would require wrapping the Java types in `Option`. (This is actually how some languages without `null`, such as Rust, provide interop with languages that have `null`.) – Kyle Strand Sep 24 '19 at 20:53
17

I think the others in the thread do a good job explaining why Some(null) "should" exist, but if you happen to be getting Some(null) somewhere and want a quick way to turn it into None, I've done this before:

scala> val x: Option[String] = Some(null)
x: Option[String] = Some(null)

scala> x.flatMap(Option(_))
res8: Option[String] = None

And when the starting Option is a legit non-null value things work as you probably want:

scala> val y: Option[String] = Some("asdf")
y: Option[String] = Some(asdf)

scala> y.flatMap(Option(_))
res9: Option[String] = Some(asdf)
overthink
  • 23,985
  • 4
  • 69
  • 69
  • 1
    Indeed the 'why' is interesting, but most of us probably are more like 'what to do next'. Thanks a lot. – fanfabbb Aug 26 '15 at 13:13
15

Much of Scala's WTFs can be attributed to its need for compatibility with Java. null is often used in Java as a value, indicating, perhaps the absence of a value. For example hashMap.get(key) will return null if the key is not matched.

With this in mind, consider the following possible values from wrapping a null returning method in an Option:

if (b) Some(hashMap.get(key)) else None
// becomes -->
None // the method was not invoked;
Some(value) // the method was invoked and a value returned; or
Some(null) // the method was invoked and null was returned.

Some(null) seems sufficiently distinct from None in this case to warrant allowing it in the language.

Of course if this is not desirable in your case then simply use:

if (b) Option(hashMap.get(key)) else None
// becomes -->
None // the method was not invoked or the mapped value was null; or
Some(value) // the method was invoked and a value returned
Synesso
  • 37,610
  • 35
  • 136
  • 207
  • "Much of Scala's WTFs can be attributed to its need for compatibility with Java"... In java there's only one instance of Optional.EMPTY. If you have an optional which has different identity than Optional.EMPTY, you are guaranteed that it will not have null wrapped. This WTF, as you call, is absent in Java but present in Scala. – Vytenis Bivainis Jan 29 '21 at 12:22
  • Not a lot of things are worse than three state booleans. – Vytenis Bivainis Jan 29 '21 at 12:23
2

Because Option is considered to be a Functor and being a Functor means:

  1. Has unit function (apply or just Option("blah") in Scala)
  2. Has map function which transforms value from T=>B but not a context
  3. Obeys 2 Functor laws - identity law and associative law

In this topic the main part is #2 - Option(1).map(t=>null) can not transform context. Some should remain. Otherwise it brakes associative law!

Just consider the following laws example:

def identity[T](v: T) = v
def f1(v: String) = v.toUpperCase
def f2(v: String) = v + v
def fNull(v: String): String = null

val opt = Option("hello")

//identity law
opt.map(identity) == opt //Some(hello) == Some(hello)

//associative law
opt.map(f1 _ andThen f2) == opt.map(f1).map(f2) //Some(HELLOHELLO) == Some(HELLOHELLO)
opt.map(fNull _ andThen f2) == opt.map(fNull).map(f2) //Some(nullnull) == Some(nullnull)

But what if Option("hello").map(t=>null) produced None? Associative law would be broken:

opt.map(fNull _ andThen f2) == opt.map(fNull).map(f2) //Some(nullnull) != None

That is my thought, might be wrong

Eugene Zhulkov
  • 505
  • 3
  • 13
2

As a simple thought experiment, consider two lists of Strings, one of length 5 and one of length 20.

Because we're running on the JVM, it's possible to insert null as a valid element into one of these lists - so put that in the long list as element #10

What, then, should the difference be in the values returned from the two following expressions?

EDIT: Exchanged get for lift, I was thinking of maps...

shortList.lift(10) //this element doesn't exist
longList.lift(10)  //this element exists, and contains null
Kevin Wright
  • 49,540
  • 9
  • 105
  • 155