shi*_*iuu 0 functional-programming scala
看来选项.contains()并不总是按预期工作。我有以下代码:
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)
Run Code Online (Sandbox Code Playgroud)
你可能期望result1并result2包含一个人,但他们实际上是空的。为什么?
事实证明以下代码有效:
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)
Run Code Online (Sandbox Code Playgroud)
我在 Scala 2.12 和 2.13 上都尝试过,结果是一样的。为什么nickName.contains不起作用?
对您问题的简短回答:发生这种情况是因为您Option contains在尝试将函数( String => Boolean) 与选项值String进行比较时使用了错误的方法。
contains但在方法上存在一些困难Option。我们看一下containsscala源码中的定义:
/**@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
Run Code Online (Sandbox Code Playgroud)
我们看到,contains将包含 的值与具有较低类型界限 的Option[A]类型的某些传递元素进行比较。这意味着类型参数或抽象类型引用 type 的超类型。这意味着我们可以传递任何类型的超类型来检查例如。在您的情况下,您尝试传递给 contains函数,但请查看定义:A1A[A1 >: A]A1A1AStringcontainsAny String => BooleanFunction1
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
Run Code Online (Sandbox Code Playgroud)
它是一种延伸的特征AnyRef(而且Any也是)。因此,对于编译器来说,这是一个合法的情况,它检查什么String是子类型Any并编译代码。在运行时,它尝试比较String值和函数 String => Boolean(String!= ),这就是始终返回您并过滤空列表String => Boolean的原因。containsfalse
因此,要解决您的主要问题 - 使用地图正确过滤列表,最好使用exists函数:
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)))
Run Code Online (Sandbox Code Playgroud)
为了保护代码的类型安全,我建议您在使用方法时设置具体类型参数contains:
// 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)
}
}
Run Code Online (Sandbox Code Playgroud)
但我们仍然不知道为什么contains有类型参数边界[A1 >: A]。为了澄清这一点,让我们看一下Option[+A]类定义:
sealed abstract class Option[+A] extends Product with Serializable { // ... }
Run Code Online (Sandbox Code Playgroud)
这里我们看到Option有协方差类型参数+A,这就是我们应该向方法添加参数边界的原因contains。在此线程中查看更多原因。
综上所述:
contains而不仅仅是在Option