例如:
scala> val l:List[String] = List("one", "two")
l: List[String] = List(one, two)
scala> l.contains(1) //wish this didn't compile
res11: Boolean = false
Run Code Online (Sandbox Code Playgroud)
在Java中以这种方式完成事情的原因的各种解释在这里似乎并不适用,因为Map和Set确实实现了类型安全版本contains和朋友.有没有办法contains在Seq上做一个类型安全的东西,不能把它克隆成一个Set?
Dan*_*wak 31
问题是Seq它的类型参数是协变的.这对其大部分功能都很有意义.作为一个不可变的容器,它确实应该是协变的.不幸的是,当他们必须定义一个采用某些参数化类型的方法时,这确实会妨碍.请考虑以下示例:
trait Seq[+A] {
def apply(i: Int): A // perfectly valid
def contains(v: A): Boolean // does not compile!
}
Run Code Online (Sandbox Code Playgroud)
问题是函数的参数类型总是逆变,返回类型中的协变.因此,该apply方法可以返回类型的值,A因为A协变性与返回类型一致apply.但是,contains 不能取类型值,A因为其参数必须是逆变的.
这个问题可以用不同的方式解决.一种选择是简单地创建A一个不变的类型参数.这允许它在协变和逆变环境中使用.然而,这种设计意味着Seq[String]将不会是一个亚型Seq[Any].另一种选择(以及最常用的选项)是采用局部类型参数,该参数在协变类型下面.例如:
trait Seq[+A] {
def +[B >: A](v: B): Seq[B]
}
Run Code Online (Sandbox Code Playgroud)
这个技巧保留了Seq[String] <: Seq[Any]属性,并在编写使用异构容器的代码时提供了一些非常直观的结果.例如:
val s: Seq[String] = ...
s + 1 // will be of type Seq[Any]
Run Code Online (Sandbox Code Playgroud)
+此示例中的函数结果是类型的值Seq[Any],因为类型Any的最小上限(LUB)String和Int(换句话说,最不常见的超类型).如果你考虑一下,这正是我们所期望的行为.如果使用both String和Intcomponents 创建序列,则其类型应为Seq[Any].
不幸的是,这个技巧虽然适用于类似的方法contains,但会产生一些令人惊讶的结果:
trait Seq[+A] {
def contains[B >: A](v: B): Boolean // compiles just fine
}
val s: Seq[String] = ...
s contains 1 // compiles!
Run Code Online (Sandbox Code Playgroud)
这里的问题是我们正在调用contains传递类型值的方法Int.阶看到这一点,并试图推断类型B这既是的超类型Int和A,在这种情况下被实例化为String.这两种类型的LUB是Any(如前所示),因此本地类型实例化contains将是Any => Boolean.因此,该contains方法似乎不是类型安全的.
这个结果不是问题,Map或者Set因为它们的参数类型都不是协变的:
trait Map[K, +V] {
def contains(key: K): Boolean // compiles
}
trait Set[A] {
def contains(v: A): Boolean // also compiles
}
Run Code Online (Sandbox Code Playgroud)
因此,简而言之,contains协变容器类型的方法不能仅限于采用组件类型的值,因为函数类型的工作方式(参数类型中的逆变).这不是Scala的限制或不好的实现,这是一个数学事实.
安慰奖是这在实践中确实不是问题.而且,正如其他答案所提到的,你可以随时定义自己的隐式转换,contains如果你真的需要额外的检查,它会增加一个"类型安全" 的方法.