为什么 Seq.contains 接受类型 Any 而不是类型参数 A? [英] Why does Seq.contains accept type Any rather than the type parameter A?

查看:50
本文介绍了为什么 Seq.contains 接受类型 Any 而不是类型参数 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 类型的值,因为 Aapply 的返回类型是协变的>.但是,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 是最小的StringInt 类型的绑定(LUB)(换句话说,最不常见的超类型).如果你仔细想想,这正是我们所期望的行为.如果您创建一个同时包含StringInt 组件的序列,那么它的类型应该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 的类型,它是 IntA 的超类型,在这种情况下被实例化为 <代码>字符串.这两种类型的 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.

这个结果对于 MapSet 来说不是问题,因为它们的参数类型都不是协变的:

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屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆