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:
- Be careful using
contains method in all containers not only in Option
- It's better to bound some methods which can be type parametrized for type-safety and more predictable behavior
- More about lower type bounds and covariance on docs.scala-lang