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].
CodePudding user response:
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
CodePudding user response:
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 String
s 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.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")