为什么 Seq.contains 接受类型 Any 而不是类型参数 A? [英] Why does Seq.contains accept type Any rather than the type parameter A?
问题描述
例如:
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
原因的各种解释在 Java 中以这种方式完成的事情 在这里似乎并不适用,因为 Map 和 Set 确实实现了 contains
和朋友的类型安全版本.有没有办法在 Seq 上执行类型安全的 contains
而不是将其克隆到 Set 中?
The various explanations of why things were done this way in Java don't seem to apply as much here, as Map and Set do implement the type-safe version of contains
and friends. Is there any way to do a type-safe contains
on a Seq short of cloning it into a Set?
推荐答案
问题在于 Seq
在其类型参数中是协变的.这对于它的大部分功能来说很有意义.作为一个不可变的容器,它确实应该是协变的.不幸的是,当他们必须定义一个采用某些参数化类型的方法时,这确实会妨碍他们.考虑以下示例:
The problem is that Seq
is covariant in its type parameter. This makes a lot of sense for the majority of its functionality. As an immutable container, it really should be covariant. Unfortunately, this does get in the way when they have to define a method which takes some of the parameterized type. Consider the following example:
trait Seq[+A] {
def apply(i: Int): A // perfectly valid
def contains(v: A): Boolean // does not compile!
}
问题在于函数的参数类型总是逆变的,而返回类型是协变的.因此,apply
方法可以返回 A
类型的值,因为 A
与 apply
的返回类型是协变的>.但是,contains
不能采用 A
类型的值,因为它的参数必须是逆变的.
The problem is that functions are always contravariant in their parameter types and covariant in their return types. Thus, the apply
method can return a value of type A
because A
is covariant along with the return type for apply
. However, contains
cannot take a value of type A
because its parameter must be contravariant.
这个问题可以用不同的方法解决.一种选择是简单地使 A
成为一个不变的类型参数.这允许它在协变和逆变上下文中使用.但是,这种设计意味着Seq[String]
将不是成为Seq[Any]
的子类型.另一种选择(也是最常用的一种)是使用局部类型参数,该参数由协变类型限制在下方.例如:
This problem can be solved in different ways. One option is to simply make A
an invariant type parameter. This allows it to be used in both covariant and contravariant contexts. However, this design would mean that Seq[String]
would not be a subtype of Seq[Any]
. Another option (and the one most often used) is to employ a local type parameter which is bounded below by the covariant type. For example:
trait Seq[+A] {
def +[B >: A](v: B): Seq[B]
}
这个技巧保留了 Seq[String] <:Seq[Any]
属性,并在编写使用异构容器的代码时提供了一些非常直观的结果.例如:
This trick retains the Seq[String] <: Seq[Any]
property as well as provides some very intuitive results when writing code which uses heterogeneous containers. For example:
val s: Seq[String] = ...
s + 1 // will be of type Seq[Any]
本例中 +
函数的结果是 Seq[Any]
类型的值,因为 Any
是最小的String
和 Int
类型的绑定(LUB)(换句话说,最不常见的超类型).如果你仔细想想,这正是我们所期望的行为.如果您创建一个同时包含String
和Int
组件的序列,那么它的类型应该是Seq[Any]
.
The results of the +
function in this example is a value of type Seq[Any]
, because Any
is the least upper-bound (LUB) for the types String
and Int
(in other words, the least-common supertype). If you think about it, this is exactly the behavior we would expect. If you create a sequence with both String
and Int
components, then its type should be Seq[Any]
.
不幸的是,这个技巧虽然适用于 contains
之类的方法,但会产生一些令人惊讶的结果:
Unfortunately, this trick, while applicable to methods like contains
, produces some surprising results:
trait Seq[+A] {
def contains[B >: A](v: B): Boolean // compiles just fine
}
val s: Seq[String] = ...
s contains 1 // compiles!
这里的问题是我们正在调用 contains
方法,传递一个 Int
类型的值.Scala 看到了这一点并试图推断 B
的类型,它是 Int
和 A
的超类型,在这种情况下被实例化为 <代码>字符串代码>.这两种类型的 LUB 是 Any
(如前面所示),因此 contains
的本地类型实例化将是 Any =>布尔值
.因此,contains
方法似乎不是类型安全的.
The problem here is that we are calling the contains
method passing a value of type Int
. Scala sees this and tries to infer a type for B
which is a supertype of both Int
and A
, which in this case is instantiated as String
. The LUB for these two types is Any
(as shown earlier), and so the local type instantiation for contains
will be Any => Boolean
. Thus, the contains
method appears to not be type safe.
这个结果对于 Map
或 Set
来说不是问题,因为它们的参数类型都不是协变的:
This result isn't an issue for Map
or Set
because neither of them are covariant in their parameter types:
trait Map[K, +V] {
def contains(key: K): Boolean // compiles
}
trait Set[A] {
def contains(v: A): Boolean // also compiles
}
所以,长话短说,协变容器类型的 contains
方法不能被限制为只获取组件类型的值,因为函数类型的工作方式(参数类型是逆变的).这并不是 Scala 的限制或糟糕的实现,这是一个数学事实.
So, long story short, the contains
method on covariant container types cannot be restricted to only take values of the component type because of the way that functions types work (contravariant in their parameter types). This isn't really a limitation of Scala or bad implementation, it's a mathematical fact.
安慰奖是这在实践中真的不是问题.而且,正如其他答案所提到的,您始终可以定义自己的隐式转换,如果您真的需要额外检查,它会添加一个类型安全"contains
之类的方法.
The consolation prize is that this really isn't an issue in practice. And, as the other answers have mentioned, you can always define your own implicit conversion which adds a "type-safe" contains
-like method if you really need the extra check.
这篇关于为什么 Seq.contains 接受类型 Any 而不是类型参数 A?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!