Reputation: 163
It seems option.contains()
does not always work as expected. I have the following code:
case class Person (name: String, nickName: Option[String])
val people = Seq(Person("Ned", Some("d")), Person("Alex", None))
val suspects = Map("c" -> 1, "d" -> 2)
val result1 = people.filter(_.nickName.contains(suspects.contains(_)))
val result2 = people.filter{ p =>
p.nickName.contains{ n =>
suspects.contains(n)
}
}
println(result1)
println(result2)
You might expect result1
and result2
contains a person, but they are actually empty. Why?
It turns out the follow code works:
val result3 = people.filter{ _.nickName match {
case Some(n) => suspects.contains(n)
case None => false
}
}
val result4 = people.filter( _.nickName.map(suspects.contains).getOrElse(false))
val result5 = people.filter( _.nickName.fold(false)(suspects.contains))
println(result3)
println(result4)
println(result5)
I have tried on both Scala 2.12 and 2.13, and results are the same. Why nickName.contains
does not work?
Upvotes: 0
Views: 3127
Reputation: 4481
Short answer on your question: it happens because you are using Option
contains
method wrong when trying to compare function (String => Boolean
) with option value which is String
.
But there are some difficulties with contains
method in Option
.
Let's take a look at contains
definition in scala source code:
/**@example {{{
* // 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
We see that contains
compares containing value of Option[A]
with some passed element with type A1
that has A
as a lower type bound [A1 >: A]
. It means the type parameter A1
or the abstract type A1
refer to a supertype of type A
. It means we can pass any supertype of String
type to check contains
for example Any
.
In your case you are trying to pass into contains function String => Boolean
but take a look at Function1
definition:
trait Function1[@specialized(scala.Int, scala.Long, scala.Float, scala.Double) -T1, @specialized(scala.Unit, scala.Boolean, scala.Int, scala.Float, scala.Long, scala.Double) +R] extends AnyRef
it is a trait which extends AnyRef
(and Any
also). So for compiler it's a legal case, it checks what String
is a subtype of Any
and compiles code. In runtime it tries to compare String
value and function String => Boolean
(String
!= String => Boolean
) and this is the reason that contains
returns you always false
and filter empty list.
So to solve your main problem - filter list correctly using map, it is better to use exists
function:
val people = Seq(Person("Ned", Some("d")), Person("Alex", None))
val suspects = Map("c" -> 1, "d" -> 2)
val resultExist = people.filter { p =>
p.nickName.exists {
suspects.contains
}
}
val resultExistUnsugar = people.filter { p =>
p.nickName.exists {
n => suspects.contains(n)
}
}
println(resultExist) // List(Person(Ned,Some(d)))
println(resultExistUnsugar) // List(Person(Ned,Some(d)))
To protect type-safety your code I would recommend you to set concrete type parameter when you are using contains
method:
// it will not compile because suspects.contains type is not String
val result1 = people.filter(_.nickName.contains[String](suspects.contains))
// it will not compile also by the same reason
val result2 = people.filter { p =>
p.nickName.contains[String] {
n => suspects.contains(n)
}
}
But we are still don't know why contains
has type parameter bounds [A1 >: A]
. To clarify this let's look at Option[+A]
class definition:
sealed abstract class Option[+A] extends Product with Serializable { // ... }
Here we see that Option
has covariance type parameter +A
and this is the reason that we should add parameter bounds to contains
method. See more in this thread why.
In conclusion:
contains
method in all containers not only in Option
Upvotes: 5