这是一个类型检查错误吗?

时间:2015-03-04 16:37:11

标签: scala pattern-matching type-safety

我把它缩小到下面的代码:

trait A[T] {
  def apply(t: T): Int
}

sealed trait P {
  def apply(): Int
}

case class I[T](a: A[T], t: T) extends P {
  def apply: Int = a(t)
}

case class X[T1, T2](a1: A[T1], a2: A[T2]) extends A[(T1, T2)] {
  def apply(t: (T1, T2)): Int =
    t match {
      case (t1, t2) => a1(t1) + a2(t2)
    }
}

object m {
  def apply(p1: P, p2: P): P =
    (p1, p2) match {
      case (I(a1, t1), I(a2, t2)) =>
        I(X(a1, a2), (t2, t1)) // <-- Here
    }
}

如您所见,我在标记为<-- Here的行中出现了类型错误。然而,代码在没有警告的情况下进行编译,并且在运行时以ClassCastException失败。代码:

case class E() extends A[Int] {
  def apply(t: Int): Int = t
}

case class S() extends A[String] {
  def apply(t: String): Int = t.length
}

object Test {
  def apply() = {
    val pe: P = I(E(), 3)
    val ps: P = I(S(), "abcd")
    val pp: P = m(pe, ps)
    pp()
  }
}

我知道当模式匹配scala有时无法检查某个值是否属于正确类型时,但这通常会导致编译器警告。

那么,这是一个错误,还是我错过了什么?

更新:我担心的是我可以犯一个类型错误,编译器甚至不会警告我。我明白(t1, t2)是正确的顺序;但如果我写得不正确,我会在执行程序之前发现它,甚至可能更晚,尽管它显然是一个类型错误。

2 个答案:

答案 0 :(得分:4)

可能缺少警告与此相关:

https://issues.scala-lang.org/browse/SI-9188

它似乎没有对A上的类型参数做任何有用的事情,除非它可以静态地证明你弄错了。

这里的最后一场比赛应警告:

scala> val i = I(E(), 42)
i: I[Int] = I(E(),42)

scala> i match { case I(a: A[Int], x) => }

scala> i match { case I(a: A[String], x) => }
<console>:15: warning: non-variable type argument String in type pattern A[String] is unchecked since it is eliminated by erasure
              i match { case I(a: A[String], x) => }
                                  ^
<console>:15: error: pattern type is incompatible with expected type;
 found   : A[String]
 required: A[Int]
              i match { case I(a: A[String], x) => }
                                  ^

scala> (i: P) match { case I(a: A[String], x) => }
<console>:15: warning: non-variable type argument String in type pattern A[String] is unchecked since it is eliminated by erasure
              (i: P) match { case I(a: A[String], x) => }
                                       ^
<console>:15: error: pattern type is incompatible with expected type;
 found   : A[String]
 required: A[Any]
Note: String <: Any, but trait A is invariant in type T.
You may wish to define T as +T instead. (SLS 4.5)
              (i: P) match { case I(a: A[String], x) => }
                                       ^

scala> (i: P) match { case I(a: A[Int], x) => }
<console>:15: warning: non-variable type argument Int in type pattern A[Int] is unchecked since it is eliminated by erasure
              (i: P) match { case I(a: A[Int], x) => }
                                       ^
<console>:15: error: pattern type is incompatible with expected type;
 found   : A[Int]
 required: A[Any]
Note: Int <: Any, but trait A is invariant in type T.
You may wish to define T as +T instead. (SLS 4.5)
              (i: P) match { case I(a: A[Int], x) => }
                                       ^

scala> (i: P) match { case I(a: A[_], x) => }

scala> (i: P) match { case I(a: A[Any], x) => }

添加:

scala> (i: P) match { case I(a: A[Any], x) => a("foo") }
java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
  at scala.runtime.BoxesRunTime.unboxToInt(BoxesRunTime.java:105)
  at E.apply(<console>:33)
  ... 33 elided

答案 1 :(得分:1)

简短回答:类型擦除。

当您在(p1, p2)上进行匹配时,您对这两种类型的所有了解都是PI[T]可能是T,但不必具有相同的I[T] { {1}}。如果您更明确地使用P,则可能会收到类型删除警告,但由于您已将类型提升到T,我的猜测是编译器没有#{1}}。甚至打扰检查警告。错误?也许。我把它称之为缺陷。在任何情况下,由于m.apply的类型信息被删除,编译器将允许这样做。

如果我们将I[T]的参数类型更改为T,那么p1p2的{​​{1}}相同,则会更加明显

object m {
  def apply[T](p1: I[T], p2: I[T]): P =
    (p1, p2) match {
      case (I(a1, t1), I(a2, t2)) =>
        I(X(a1, a2), (t2, t1))
  }
}

val pe = I(E(), 3)
val ps = I(S(), "abcd")

m(pe, pe).apply // same underlying type, works
m(ps, ps).apply // same underlying type, works
m(pe, ps).apply // doesn't compile

不管你最终想要的是什么,我都不知道。