Reputation: 11266
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
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
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
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
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
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