为什么Seq.containsSlice不是类型安全的?

时间:2013-11-29 15:10:37

标签: scala scala-collections

在调试我的程序时,我发现我错误地为containsSlice的{​​{1}}方法提供了错误的参数。我提供的Seq[Char]当然从未包含在Seq[Boolean]

编译器为什么让我这样做?这种方法不能在Scala集合库中以类型安全的方式实现吗?我认为必须刻意做到能够提供Seq[Char],理论上可以是Seq[Any]。但设计师是否应该保持此方法的类型安全,并添加一些非类型安全的方法,如Char

修改: 我尝试在集合类层次结构之外创建自己的类型安全containsAnySlice方法,但由于某种原因,containsSlice方法中的代码编译,而我希望它不会:

test

我想这是因为object Seqs { def containsSlice[A, B <: A](s1: Seq[A], s2: Seq[B]): Boolean = { s1.containsSlice(s2) } def test = { val sc: Seq[Char] = Seq('A','B','C') val sb: Seq[Boolean] = Seq(true, false) containsSlice(sc, sb) } } CharBoolean)的常见超类型是针对类型参数AnyVal推断的,我需要显式调用{{1如果我不想要那个(但我可以使用A)。

为了使它不能为不兼容的containsSlice[Char](sc, sb)编译,我能做到这一点的唯一方法是使用2个参数列表,以便推断出正确的类型:

sc.containsSlice[Char](sb)

2 个答案:

答案 0 :(得分:3)

问题是Seq在其类型参数中被声明为covaraint:trait Seq[+A]这意味着如果Seq[A]Seq[B]的子类,AB的子类。 containsSlicetrait MySeq[+A] { def containsSlice(as: Seq[A]): Boolean } 的参数自然处于反变量位置,这意味着它不能与类型参数A匹配,因为A已经被声明为协变。试着自己写一下:

error: covariant type A occurs in contravariant position in type Seq[A] of value as
  def containsSlice(as: Seq[A]): Boolean
                    ^
one error found

编译器会抱怨:

Seq[Char]

如果你玩这个,你怎么可能无论如何都不能保证类型安全。让我们假装编译器允许这个定义。在您的示例中,您有一个val myseq: Seq[Char] = List('a','b','c') val anyseq: Seq[Any] = myseq 。因为Seq的协方差,这应该是允许的:

Seq[Char]

我们被允许这样做是因为Seq[Any]Char的子类型,因为AnycontainsSlice的子类型。现在,anyseq上Seq[Any]的类型是什么?由于Seqanyseq的类型参数为Any,因此{{1}}为{{1}},因此您的类型安全性已消失。

答案 1 :(得分:3)

containsSlice只是对indexOfSlice的调用,具有以下签名:

indexOfSlice[B >: A](that: GenSeq[B]): Int

containsSlice可以做同样的事情。

使用下限来约束类型参数是很有用的,所以你可以这样做:

apm@mara:~/tmp$ scalam -Ywarn-infer-any 
Welcome to Scala version 2.11.0-M7 (OpenJDK 64-Bit Server VM, Java 1.7.0_25).
Type in expressions to have them evaluated.
Type :help for more information.

scala> "abcdef" contains 8
<console>:8: warning: a type was inferred to be `AnyVal`; this may indicate a programming error.
              "abcdef" contains 8
                                ^
res0: Boolean = false

警告告诉您类型安全性已受到损害。

-Xlint包括-Ywarn-infer-any

但请查看:

scala> "abcdef" indexOfSlice (1 to 10)
res1: Int = -1

没有警告?

您可以启用花哨的新-Ytyper-debug来查看:

|    |    |    |    |    \-> [B >: Char](that: scala.collection.GenSeq[B], from: Int)Int <and> [B >: Char](that: scala.collection.GenSeq[B])Int
|    |    |    |    solving for (B: ?B)

|    |    |    |    solving for (B: ?B)
|    |    |    |    \-> Int

这似乎意味着在存在重载的情况下,您不会收到警告。

这是因为如何对重载进行类型检查。

-Xprint:typer确认,当然,你结束AnyVal,但必须在以后发生:

private[this] val res0: Int = scala.this.Predef.augmentString("abcdef").indexOfSlice[AnyVal](scala.this.Predef.intWrapper(1).to(10));

我们真的不需要另一个原因,即重载是邪恶的,但你知道println也是邪恶的吗?

scala> println("abcdef" contains 8)
false

没有警告。

事实证明警告非常保守:

  // Can warn about inferring Any/AnyVal as long as they don't appear
  // explicitly anywhere amongst the formal, argument, result, or expected type.

由于println需要Any,这是我们预期的表达式类型,我们会失去警告。

总之,

import reflect.runtime._
import universe._

case class Foo[+A](a: A) {
  def f[B >: A : TypeTag](other: Foo[B]): String = typeTag[B].toString
  def g[B] = "huh"
  def j[B >: A : TypeTag](other: Foo[B]): String = typeTag[B].toString
  def j[B >: A : TypeTag](other: Foo[B], dummy: Int): String = typeTag[B].toString
}

object Test extends App {
  val x = Foo("abc") f Foo(3)       // warn
  val y = Foo("abc").g
  val w = Foo("abc") j Foo(3)       // nowarn
  val z = Foo("abc") j (Foo(3), 0)
  Console println s"$x $y $w $z"
  val b = "abcdef" contains 8       // warn
  println("abcdef" contains 8)      // nowarn

  implicit class Sectional[+A](val as: Seq[A]) {
    def containsSection[B >: A](other: Seq[B]): Boolean = as containsSlice other
  }
  ("abcdefg": Seq[Char]) containsSection (1 to 10)  // warn
  implicit class Sectional2(val as: String) {
    def containsSection[B >: String](other: Seq[B]): Boolean = as containsSlice other
  }
  "abcdefg" containsSection (1 to 10)  // warn
}

特殊套管字符串太糟糕了。