首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >为什么Seq.contains接受类型Any而不是类型参数A?

为什么Seq.contains接受类型Any而不是类型参数A?
EN

Stack Overflow用户
提问于 2010-01-17 01:18:33
回答 4查看 2.7K关注 0票数 13

例如:

代码语言:javascript
运行
复制
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 

由于Map和Set实现了类型安全版本的contains和friends,因此The various explanations of why things were done this way in Java在这里的应用似乎不多。除了将其克隆到Set中之外,还有什么方法可以在Seq上执行类型安全的contains呢?

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2010-01-17 03:13:19

问题是Seq的类型参数是协变的。这对于它的大部分功能来说是很有意义的。作为一个不可变的容器,它真的应该是协变的。不幸的是,当他们必须定义一个接受一些参数化类型的方法时,这确实会妨碍他们。考虑以下示例:

代码语言:javascript
运行
复制
trait Seq[+A] {
  def apply(i: Int): A       // perfectly valid

  def contains(v: A): Boolean   // does not compile!
}

问题是函数的参数类型总是逆变的,返回类型总是协变的。因此,apply方法可以返回A类型的值,因为Aapply的返回类型是协变的。但是,contains不能接受A类型的值,因为它的参数必须是逆变量。

这个问题可以用不同的方式解决。一种选择是简单地将A设置为不变类型参数。这使得它既可以用于协变上下文,也可以用于逆变上下文。但是,这种设计意味着Seq[String]不会是Seq[Any]的子类型。另一种选择(也是最常用的一种)是使用局部类型参数,该参数在下面由协变类型限定。例如:

代码语言:javascript
运行
复制
trait Seq[+A] {
  def +[B >: A](v: B): Seq[B]
}

这个技巧保留了Seq[String] <: Seq[Any]属性,并在编写使用异构容器的代码时提供了一些非常直观的结果。例如:

代码语言:javascript
运行
复制
val s: Seq[String] = ...
s + 1      // will be of type Seq[Any]

本例中的+函数的结果是一个Seq[Any]类型的值,因为AnyStringInt类型(换句话说,最不常见的超类型)的最小上界(LUB)。如果你仔细想想,这正是我们所期望的行为。如果创建的序列同时包含StringInt组件,则其类型应为Seq[Any]

不幸的是,这个技巧,虽然适用于像contains这样的方法,但产生了一些令人惊讶的结果:

代码语言:javascript
运行
复制
trait Seq[+A] {
  def contains[B >: A](v: B): Boolean    // compiles just fine
}

val s: Seq[String] = ...
s contains 1        // compiles!

这里的问题是,我们调用的是传递Int类型的值的contains方法。Scala看到了这一点,并尝试推断B的类型,它是IntA的超类型,在本例中被实例化为String。这两种类型的LUB是Any (如前所述),因此contains的本地类型实例化将是Any => Boolean。因此,contains方法似乎不是类型安全的。

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

代码语言:javascript
运行
复制
trait Map[K, +V] {
  def contains(key: K): Boolean    // compiles
}

trait Set[A] {
  def contains(v: A): Boolean      // also compiles
}

因此,长话短说,协变容器类型上的contains方法不能被限制为只接受组件类型的值,因为函数类型的工作方式(参数类型中的逆变)。这并不是Scala的局限性或糟糕的实现,而是一个数学事实。

值得安慰的是,这在实践中真的不是问题。而且,正如其他答案所提到的,如果您确实需要额外的检查,您可以定义自己的隐式转换,它添加了一个“类型安全”的contains-like方法。

票数 32
EN

Stack Overflow用户

发布于 2010-01-17 02:10:20

我不确定为什么要这样设计--可能是为了反映Java中的某些东西。

无论如何,使用pimp-my-library模式比克隆到一个集合中更有效:

代码语言:javascript
运行
复制
class SeqWithHas[T](s: Seq[T]) {
  def has(t: T) = s.contains(t)
}
implicit def seq2seqwithhas[T](s: Seq[T]) = new SeqWithHas(s)

scala> List("3","5") has 1
<console>:7: error: type mismatch;
 found   : Int(1)
 required: java.lang.String
       List("3","5") has 1
                         ^

scala> List("3","5") has "1"
res1: Boolean = false

(您可能希望将这些内容和其他方便的内容放入单个对象中,然后在大多数源文件中导入MyHandyObject._。)

票数 4
EN

Stack Overflow用户

发布于 2010-01-17 02:38:46

如果您愿意放弃infix,转而使用常规的方法调用,那么定义并导入下面的has(...)方法将避免每次需要类型安全的"has“测试时创建一个实例(例如,值得在内部循环中使用):

代码语言:javascript
运行
复制
def has[T](s: Set[T], t: T) = s.contains(t)

自然,SetT可以简化为具有contains方法的最不具体的类型。

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/2078246

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档