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.
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.
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.
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.
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
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].
Related
I just discovered something weird. This statement:
Some("test this").contains("test")
Evaluates to false. While this evaluates to true:
Some("test this").contains("test this")
How does this make sense? I thought the Option would run the contains on the wrapped object if possible.
EDIT:
I'm also thinking about this from a code readability perspective. Imagine you are seeing this code:
person.name.contains("Roger")
Must name be equal to Roger? Or can it contain Roger? The behavior depends if it's a String or Option[String].
There's a principle in typed functional programming called "parametric reasoning". Broadly stated, the principle is that it's desirable to be able to have intuitions about what a function does just from looking at its type signature.
If we "devirtualize" (effectively turning it into a static method... this is actually a fairly common optimization step in object-oriented runtimes) Option's contains method has the signature:
def contains[A, A1 >: A](opt: Option[A], elem: A1): Boolean
That is, it takes an Option[A] and an A1 (where A1 is a supertype of A, if it's not an A) and returns a Boolean. Implicitly in Scala's typesystem, of course, we know that A and A1 are both subtypes of Any.
Without knowing anything more about what the types A and A1 are (A might be String and A1 might be AnyRef, or A and A1 might both be Int: whatever our intuition, it has to apply as much in either situation), what could we possibly do? We're basically limited to combinations of operations involving an Option[Any] and an Any which eventually get us to a Boolean (and, ideally, won't throw an exception).
For instance, opt.nonEmpty && opt.get == elem works: we can always call nonEmpty on an Option[Any] and then compare the contents using equality. We could also do something like opt.isEmpty || (opt.get.## % 43) == (elem.## % 57), but knowing that the contents of the Option and some other object have equal remainders in two different bases doesn't strike one as useful.
Note that in your specific case, because there's no contains method on an Any. What should the behavior be if we have an Option[Int]?
It might actually be useful, since we do have the ability to convert arbitrary objects into Strings via the toString method (thank you Java!), to implement a containsSubstring method on Option[A]:
def containsSubstring(substring: String): Boolean =
nonEmpty && get.toString.contains(substring)
You could implement an enrichment class along these lines:
object Enrichments {
implicit class OptionOps[A](opt: Option[A]) extends AnyVal {
def containsSubstring(substring: String): Boolean =
opt.nonEmpty && opt.get.toString.contains(substring)
}
}
then you only need:
import Enrichments.OptionOps
Some("test this").containsSubstring("test") // evaluates true
case class Person(name: Option[String], age: Int)
// Option(p).containsSubstring("Roger") would also work, assuming Person doesn't override toString...
def isRoger(p: Person): Boolean = p.name.containsSubstring("Roger")
I recommend to check the docs and eventually the code of the API that you are you using. The docs detail what Option's contains does and how it works:
/** Tests whether the option contains a given value as an element.
*
* This is equivalent to:
*
* option match {
* case Some(x) => x == elem
* case None => false
* }
*
* // Returns true because Some instance contains string "something" which equals "something".
* Some("something") contains "something"
*
* // Returns false because "something" != "anything".
* Some("something") contains "anything"
*
* // Returns false when method called on None.
* None contains "anything"
*
* #param elem the element to test.
* #return `true` if the option has an element that is equal (as
* determined by `==`) to `elem`, `false` otherwise.
*/
final def contains[A1 >: A](elem: A1): Boolean =
!isEmpty && this.get == elem
Why is the output of this comparison outputs true?
import scala.collection.immutable.ListSet
Set(1) == ListSet(1) // Expect false
//Output
res0: Boolean = true
And in a more general sense, how the comparison is actually done?
Since the inheritance chain Set <: GenSet <: GenSetLike is a bit lengthy, it might be not immediately obvious where to look for the code of equals, so I thought maybe I quote it here:
GenSetLike.scala:
/** Compares this set with another object for equality.
*
* '''Note:''' This operation contains an unchecked cast: if `that`
* is a set, it will assume with an unchecked cast
* that it has the same element type as this set.
* Any subsequent ClassCastException is treated as a `false` result.
* #param that the other object
* #return `true` if `that` is a set which contains the same elements
* as this set.
*/
override def equals(that: Any): Boolean = that match {
case that: GenSet[_] =>
(this eq that) ||
(that canEqual this) &&
(this.size == that.size) &&
(try this subsetOf that.asInstanceOf[GenSet[A]]
catch { case ex: ClassCastException => false })
case _ =>
false
}
Essentially, it checks whether the other object is also a GenSet, and if yes, it attempts to perform some fail-fast checks (like comparing size and invoking canEqual), and if the sizes are equal, it checks whether this set is a subset of another set, presumably by checking each element.
So, the exact class used to represent the set at runtime is irrelevant, what matters is that the compared object is also a GenSet and has the same elements.
From Scala collections equality:
The collection libraries have a uniform approach to equality and hashing. The idea is, first, to divide collections into sets, maps, and sequences.
...
On the other hand, within the same category, collections are equal if and only if they have the same elements
In your case, both collections are considered sets and they contain the same elements, hence, they're equal.
Scala 2.12.8 documentation:
This class implements immutable sets using a list-based data
structure.
So ListSet is a set too but with concrete (list-based) implementation.
I have this two functions
def pattern(s: String): Option[Pattern] =
try {
Some(Pattern.compile(s))
} catch {
case e: PatternSyntaxException => None
}
and
def mkMatcher(pat: String): Option[String => Boolean] =
pattern(pat) map (p => (s: String) => p.matcher(s).matches)
Map is the higher-order function that applies a given function to each element of a list.
Now I am not getting that how map is working here as per above statement.
Map is the higher-order function that applies a given function to each element of a list.
This is an uncommonly restrictive definition of map.
At any rate, it works because it was defined by someone who did not hold to that.
For example, that someone wrote something akin to
sealed trait Option[+A] {
def map[B](f: A => B): Option[B] = this match {
case Some(value) => Some(f(value))
case None => None
}
}
as part of the standard library. This makes map applicable to Option[A]
It was defined because it makes sense to map many kinds of data structures not just lists.
Mapping is a transformation applied to the elements held by the data structure.
It applies a function to each element.
Option[A] can be thought of as a trivial sequence. It either has zero or one elements. To map it means to apply the function on its element if it has one.
Now it may not make much sense to use this facility all of the time, but there are cases where it is useful.
For example, it is one of a few distinct methods that, when present enable enable For Expressions to operate on a type. Option[A] can be used in for expressions which can be convenient.
For example
val option: Option[Int] = Some(2)
val squared: Option[Int] = for {
n <- option
if n % 2 == 0
} yield n * n
Interestingly, this implies that filter is also defined on Option[A].
If you just have a simple value it may well be clearer to use a less general construct.
Map is working the same way that it does with other collections types like List and Vector. It applies your function to the contents of the collection, potentially changing the type but keeping the collection type the same.
In many cases you can treat an Option just like a collection with either 0 or 1 elements. You can do a lot of the same operations on Option that you can on other collections.
You can modify the value
var opt = Option(1)
opt.map(_ + 3)
opt.map(_ * math.Pi)
opt.filter(_ == 1)
opt.collect({case i if i > 0 => i.toString })
opt.foreach(println)
and you can test the value
opt.contains(3)
opt.forall(_ > 0)
opt.exists(_ > 0)
opt.isEmpty
Using these methods you rarely need to use a match statement to unpick an Option.
While going though Functional Programming in Scala, I came across the following code snippet:
def foldRight[A](z: => B)(f: (A,=>B) => B):B = uncons match {
case Some((h,t)) => f(h,t.foldRight(z)(f))
case None => z
}
The authors then go ahead and state the following:
This looks very similar to the foldRight we wrote for List, but
notice how our combining function, f, is non-strict in its second
parameter. If f chooses not to evaluate its second parameter, this
terminates the traversal early. We can see this by using foldRight to
implement exists, which checks to see if any value in the Stream
matches a given predicate.
Then the author states the following:
def exists(p: A => Boolean): Boolean =
foldRight(false)((a, b) => p(a) || b)
My question is how does the combining function f causes early termination in the exists method? I don't think I was able to understand how that happens from the text.
In f(h,t.foldRight(z)(f)), the first argument provided to f is h, the second argument is t.foldRight(z)(f). The way foldRight is defined is that the second argument of its f argument is a by-name argument which will not be evaluated until needed (and will be evaluated everytime it is needed). So in f: (A, =>B) => B, the first argument of type A is a normal argument, but the second one of type B is a by-name argument.
So pretend you defined f like this:
f(a: A, b: => Boolean): Boolean = predicate(a) || b
If predicate(a) is true then b is never needed and will never be evaluated. That is the way the or operator works.
So say for exists applied to some Stream. For the first element to be uncons-ed that will exist (where p(h) is true) this code:
uncons match {
case Some((h,t)) => f(h,t.foldRight(z)(f))
case None => z
}
Is the same as this code (we assume we have a non-empty stream, so we can remove the second case):
f(h,t.foldRight(z)(f))
Which is then equivalent to (expanding f):
p(h) || t.foldRight(z)(f)
But p(h) is true so:
true || t.foldRight(z)(f)
And that's the same as just true and no need to continue calling foldRight, so early termination happens!
I am curious:
scala> Some(null) == None
res10: Boolean = false
Why isn't Some(null) transformed to None?
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.
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.
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)
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
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
Because Option is considered to be a Functor and being a Functor means:
Has unit function (apply or just Option("blah") in Scala)
Has map function which transforms value from T=>B but not a context
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