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

时间:2010-01-16 17:18:33

标签: scala

例如:

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 

The various explanations of why things were done this way in Java似乎没有在这里应用那么多,因为Map和Set确实实现了contains和朋友的类型安全版本。有没有办法在Seq上做一个类型安全的contains,而不是将它克隆到Set中?

4 个答案:

答案 0 :(得分:30)

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

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]的子类型。另一种选择(以及最常用的选项)是采用局部类型参数,该参数在协变类型下面。例如:

trait Seq[+A] {
  def +[B >: A](v: B): Seq[B]
}

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

val s: Seq[String] = ...
s + 1      // will be of type Seq[Any]

此示例中的+函数的结果是类型Seq[Any]的值,因为Any是类型String的最小上限(LUB)和Int(换句话说,最不常见的超类型)。如果你考虑一下,这正是我们所期望的行为。如果您创建包含StringInt组件的序列,则其类型Seq[Any]

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

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的超类型,在这种情况下实例化为String。这两种类型的LUB是Any(如前所示),因此contains的本地类型实例化将为Any => Boolean。因此,contains方法出现不是类型安全的。

此结果不是MapSet的问题,因为它们的参数类型都不是协变的:

trait Map[K, +V] {
  def contains(key: K): Boolean    // compiles
}

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

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

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

答案 1 :(得分:4)

我不确定为什么事情是这样设计的 - 可能是用Java镜像一些东西。

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

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._。)

答案 2 :(得分:3)

如果您愿意放弃中缀以支持常规方法调用,定义和导入以下has(...)方法将避免每次需要类型安全“has”测试时创建实例(值得在内循环中,例如:)

def has[T](s: Set[T], t: T) = s.contains(t)

当然,Set [T]可以放宽到具有contains方法的最小特定类型。

答案 3 :(得分:0)

通过提供类型相等的证据,

X = select count(1) from table where QUARTER(`Creation_Date`) = 1 and Customer = 'X' and Status = 'Available';
Y = select count(1) from table where QUARTER(`Creation_Date`) = 1 and Customer = 'X';
Z = X/Y*100