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

cuxqih21  于 2023-10-18  发布在  Scala
关注(0)|答案(5)|浏览(161)

举例来说:

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中?

pbgvytdp

pbgvytdp1#

问题是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]类型的值,因为AnyStringInt类型的最小上限(LUB)(换句话说,最不常见的超类型)。如果你仔细想想,这正是我们所期望的行为。如果你创建一个同时包含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类方法。

flseospp

flseospp2#

我不知道为什么要这样设计--可能是为了反映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

(You我可能想把这些东西和其他方便的东西放在一个对象中,然后在大多数源文件中导入MyHandyObject._。)

hujrc8aj

hujrc8aj3#

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

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

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

fbcarpbf

fbcarpbf4#

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

def contains[A,B](xs: List[A], x: B)(implicit ev: A =:= B) = xs.contains(x)
juzqafwq

juzqafwq5#

在回答关于类型安全的contains的第二个问题时,如果您正在使用cats,则contains_在UnorderedFoldables上可用:

import cats.syntax.unorderedFoldable._

val mySeq = Seq(1, 2, 3)

mySeq.contains_(1)  // fine

mySeq.contains_("1")  // compile error

相关问题