Hakan Serce
Hakan Serce

Reputation: 11266

Why does Some(x).map(_ => null) not evaluate to None?

I have recently faced a confusing issue in Scala. I expect the following code to result in None, but it results in Some(null):

Option("a").map(_ => null)

What is the reasoning behind this? Why does it not result in None?

Note: This question is not a duplicate of Why Some(null) isn't considered None?, as that questions asks for explicitly using Some(null). My question is about using Option.map.

Upvotes: 1

Views: 1533

Answers (5)

marcospereira
marcospereira

Reputation: 12202

Here is the code for Option map method:

/** Returns a $some containing the result of applying $f to this $option's
 * value if this $option is nonempty.
 * Otherwise return $none.
 *
 *  @note This is similar to `flatMap` except here,
 *  $f does not need to wrap its result in an $option.
 *
 *  @param  f   the function to apply
 *  @see flatMap
 *  @see foreach
 */
@inline final def map[B](f: A => B): Option[B] =
  if (isEmpty) None else Some(f(this.get))

So, as you can see, if the option is not empty, it will map to Some with the value returned by the function. And here is the code for Some class:

/** Class `Some[A]` represents existing values of type
 *  `A`.
 *
 *  @author  Martin Odersky
 *  @version 1.0, 16/07/2003
 */
@SerialVersionUID(1234815782226070388L) // value computed by serialver for 2.11.2, annotation added in 2.11.4
final case class Some[+A](x: A) extends Option[A] {
  def isEmpty = false
  def get = x
}

So, as you can see, Some(null) will actually create a Some object containing null. What you probably want to do is use Option.apply which does returns a None if the value is null. Here is the code for Option.apply method:

/** An Option factory which creates Some(x) if the argument is not null,
 *  and None if it is null.
 *
 *  @param  x the value
 *  @return   Some(value) if value != null, None if value == null
 */
def apply[A](x: A): Option[A] = if (x == null) None else Some(x)

So, you need to write your code like this:

Option("a").flatMap(s => Option.apply(null))

Of course, this code makes no sense, but I will consider that you are just doing some kind of experiment.

Upvotes: 2

Chris Martin
Chris Martin

Reputation: 30756

Every time we add an exception to a rule, we deprive ourselves of a tool for reasoning about code.

Mapping over a Some always evaluates to a Some. That's a simple and useful law. If we were to make the change you propose, we would no longer have that law. For example, here's a thing we can say with certainty. For all f, x, and y:

Some(x).map(f).map(_ => y) == Some(y)

If we were to make the change you propose, that statement would no longer be true; specifically, it would not hold for cases where f(x) == null.

Moreover, Option is a functor. Functor is a useful generalization of things that have map functions, and it has laws that correspond well to intuition about how mapping should work. If we were to make the change you propose, Option would no longer be a functor.

null is an aberration in Scala that exists solely for interoperability with Java libraries. It is not a good reason to discard Option's validity as functor.

Upvotes: 9

maasg
maasg

Reputation: 37435

To understand what's going on, we can use the functional substitution principle to explore the given expression step by step:

Option("a").map(s => null) // through Option.apply
Some("a").map(s => null) // let's name the anonymous function as: f(x) = null 
Some("a").map(x => f(x))  // following Option[A].map(f:A=>B) => Option[B]
Some(f("a"))  // apply f(x)
Some(null)

The confusion expressed in the question comes from the assumption that the map would apply to the argument of the Option before the Option.apply is evaluated: Let's see how that couldn't possibly work:

Option("a").map(x=> f(x)) // !!! can't evaluate map before Option.apply. This is the key to understand !
Option(f(a))              // !!! we can't get here
Option(null)              // !!! we can't get here
None                      // !!! we can't get here

Upvotes: 2

Łukasz
Łukasz

Reputation: 8673

Option is kind of replacement for null, but in general you see null in scala when you are talking to some java code, it is not like Option is supposed to handle nulls whenever possible, it is not designed to be used with nulls but instead of them. There is however conveniece method Option.apply that is similar to java's Optional.ofNullable that would handle the null case, and that's mostly all about nulls and Options in scala. In all other cases it works on Some and None not making any difference if null is inside or not.

If you have some nasty method returning null that comes from java and you want to use it directly, use following approach:

def nastyMethod(s: String): String = null

Some("a").flatMap(s => Option(nastyMethod(s)))
// or
Some("a").map(nastyMethod).flatMap(Option(_))

Both output Option[String] = None

So, nastyMethod can return a String or null conceptually is an Option, so wrap its result in an Option and use it as an Option. Don't expect null magic will happen whenever you need it.

Upvotes: 2

Milyardo
Milyardo

Reputation: 1

Why would it be None, the signature of map is a function from a value A to B to yield an Option[B]. No where in that signature does it indicate that B may be null by saying B is an Option[B]. flatMap however does indicate that the values returned is also optional. It's signature is Option[A] => (A => Option[B]) => Option[B].

Upvotes: 0

Related Questions