我试图用普通的类和伴生对象替换case类,但突然得到了类型错误。
编译良好的代码(合成示例):
trait Elem[A,B] {
def ::[C](other : Elem[C,A]) : Elem[C,B] = other match {
case Chain(head, tail) => Chain(head, tail :: this)
case simple => Chain(simple, this)
}
}
class Simple[A,B] extends Elem[A,B]
final case class Chain[A,B,C](head : Elem[A,B], tail : Elem[B,C]) extends Elem[A,C]使用以下命令更改最后一个定义:
final class Chain[A,B,C](val head : Elem[A,B], val tail : Elem[B,C]) extends Elem[A,C]
object Chain {
def unapply[A,B,C](src : Chain[A,B,C]) : Option[(Elem[A,B], Elem[B,C])] =
Some( (src.head, src.tail) )
def apply[A,B,C](head : Elem[A,B], tail : Elem[B,C]) : Chain[A,B,C] =
new Chain(head, tail)
}但是这些看似等价代码会使编译器产生错误:
CaseMystery.scala:17: error: type mismatch;
found : test.casemystery.Fail.Elem[A,B] where type B, type A >: C <: C
required: test.casemystery.Fail.Elem[A,Any] where type A >: C <: C
Note: B <: Any, but trait Elem is invariant in type B.
You may wish to define B as +B instead. (SLS 4.5)
case Chain(head, tail) => Chain(head, tail :: this)
^
CaseMystery.scala:17: error: type mismatch;
found : test.casemystery.Fail.Elem[B(in method ::),B(in trait Elem)] where type B(in method ::)
required: test.casemystery.Fail.Elem[Any,B(in trait Elem)]
Note: B <: Any, but trait Elem is invariant in type A.
You may wish to define A as +A instead. (SLS 4.5)
case Chain(head, tail) => Chain(head, tail :: this)
^
two errors found使用case语句隐式创建的方法和为普通类显式编写的方法有什么区别?
发布于 2016-08-26 12:15:33
这个答案最终比我预期的要长。如果您只想了解类型推断发生了什么,请跳到末尾。否则,您将被引导完成获得答案的步骤。
问题出在case中,而不是case class中
在这种情况下,尽管我不愿意承认这一点,但案例类真的很神奇。特别是,他们在类型检查器级别得到了特殊对待(我想我们可以同意,如果你的代码通过了这个阶段,它就会工作--你甚至可以向它抛出足够的类型转换来让它工作)。
令人惊讶的是,问题不是在类Chain本身,而是在使用它的地方,特别是在模式匹配部分。例如,考虑case类
case class Clazz(field: Int)然后,您希望以下内容是等效的:
Clazz(3) match { case Clazz(i) => i }
// vs
val v = Clazz.unapply(Clazz(3))
if (v.isDefined) v.get else throw new Exception("No match")但是,Scala想要更聪明并优化这一点。特别是,这个unapply方法几乎永远不会失败(让我们暂时忽略null ),并且可能会被大量使用,所以Scala希望完全避免使用它,而只是像通常获取对象的任何成员一样提取字段。正如我的编译器教授喜欢说的那样,“编译器是作弊而不被抓住的艺术”。
然而,这里的类型检查器是不同的。问题出在
def ::[Z, X](other : Elem[Z, X]) : Elem[Z, Y] = other match {
case Chain(head, tail) => Chain(head, tail :: this)
case simple => Chain(simple, this)
}如果你用-Xprint:typer编译,你会看到类型检查器看到了什么。case类的版本为
def ::[C](other: Elem[C,A]): Elem[C,B] = other match {
case (head: Elem[C,Any], tail: Elem[Any,A])Chain[C,Any,A]((head @ _), (tail @ _)) => Chain.apply[C, Any, B](head, {
<synthetic> <artifact> val x$1: Elem[Any,A] = tail;
this.::[Any](x$1)
})
case (simple @ _) => Chain.apply[C, A, B](simple, this)
}而常规类具有
def ::[C](other: Elem[C,A]): Elem[C,B] = other match {
case Chain.unapply[A, B, C](<unapply-selector>) <unapply> ((head @ _), (tail @ _)) => Chain.apply[A, Any, B](<head: error>, {
<synthetic> <artifact> val x$1: Elem[_, _ >: A <: A] = tail;
this.::[B](x$1)
})
case (simple @ _) => Chain.apply[C, A, B](simple, this)
}所以类型检查器实际上得到了一个不同的(特殊的) case结构。
那么match会被转换成什么呢?
只是为了好玩,我们可以检查下一阶段-Xprint:patmat会发生什么,这会扩展出模式(尽管在这里,这些不再是真正有效的Scala程序的事实真的变得令人痛苦)。首先,case类具有
def ::[C](other: Elem[C,A]): Elem[C,B] = {
case <synthetic> val x1: Elem[C,A] = other;
case5(){
if (x1.isInstanceOf[Chain[C,Any,A]])
{
<synthetic> val x2: Chain[C,Any,A] = (x1.asInstanceOf[Chain[C,Any,A]]: Chain[C,Any,A]);
{
val head: Elem[C,Any] = x2.head;
val tail: Elem[Any,A] = x2.tail;
matchEnd4(Chain.apply[C, Any, B](head, {
<synthetic> <artifact> val x$1: Elem[Any,A] = tail;
this.::[Any](x$1)
}))
}
}
else
case6()
};
case6(){
matchEnd4(Chain.apply[C, A, B](x1, this))
};
matchEnd4(x: Elem[C,B]){
x
}
}尽管这里有很多东西令人困惑,但请注意,我们从未使用过unapply方法!对于非case类版本,我将使用user1303559中的工作代码:
def ::[Z, XX >: X](other: Elem[Z,XX]): Elem[Z,Y] = {
case <synthetic> val x1: Elem[Z,XX] = other;
case6(){
if (x1.isInstanceOf[Chain[A,B,C]])
{
<synthetic> val x2: Chain[A,B,C] = (x1.asInstanceOf[Chain[A,B,C]]: Chain[A,B,C]);
{
<synthetic> val o8: Option[(Elem[A,B], Elem[B,C])] = Chain.unapply[A, B, C](x2);
if (o8.isEmpty.unary_!)
{
val head: Elem[Z,Any] = o8.get._1;
val tail: Elem[Any,XX] = o8.get._2;
matchEnd5(Chain.apply[Z, Any, Y](head, {
<synthetic> <artifact> val x$1: Elem[Any,XX] = tail;
this.::[Any, XX](x$1)
}))
}
else
case7()
}
}
else
case7()
};
case7(){
matchEnd5(Chain.apply[Z, XX, Y](x1, this))
};
matchEnd5(x: Elem[Z,Y]){
x
}
}在这里,果然,unapply方法出现了。
这并不是真正的作弊(对于专业人士来说)
当然,Scala实际上并没有作弊--这些行为都在规范中。特别是,我们看到case类受益于constructor patterns是一种特殊的东西,因为它们是irrefutable (与我上面所说的Scala不想使用unapply方法有关,因为它“知道”它只是在提取字段)。
然而,真正让我们感兴趣的部分是8.3.2 Type parameter inference for constructor patterns。常规类和case类之间的区别在于,当Chain是case类时,Chain模式是一个“构造函数模式”,而在其他情况下,它只是一个常规模式。构造器模式
other match {
case Chain(head, tail) => Chain(head, tail :: this)
case simple => Chain(simple, this)
}最终得到的类型就像是
other match {
case _: Chain[a1,a2,a3] => ...
}然后,根据参数类型的other: Elem[C,A]和Chain[a1,a2,a3] extends Elem[a1,a3]的事实,我们得到a1是C,a3是A,a2可以是任何东西,Any也是。因此,为什么case类的-Xprint:typer输出中的类型中包含Chain[C,Any,A]。这将执行类型检查。
但是,构造函数模式是特定于case类的,所以没有办法在这里模仿case类的行为。
构造函数模式的形式为
c(p1,…,pn)wheren≥0。它由一个稳定的标识符c和后面的元素模式p1,…,pn组成。构造函数c是表示case class的简单或限定名称。
发布于 2016-08-19 16:18:58
首先,other是Elem[C, A],但在您尝试将其作为Chain(head, tail)进行匹配之后,它实际上与Chain[C, some inner B, A](head: Elem[C, inner B], tail: Elem[inner B, A])匹配。之后,您将创建Chain[C, inner B <: Any, A](head: Elem[C, inner B], (tail :: this): Elem[inner B, B])
但结果类型必须为Elem[C, B]或Chain[C, Any, B]。所以编译器试图将inner B转换为Any。但是因为inner B是不变的,所以你必须恰好有Any。
这实际上更好的重写如下:
trait Elem[X, Y] {
def ::[Z, X](other : Elem[Z, X]) : Elem[Z, Y] = other match {
case Chain(head, tail) => Chain(head, tail :: this)
case simple => Chain(simple, this)
}
}
final class Chain[A, B, C](val head : Elem[A, B], val tail : Elem[B, C]) extends Elem[A, C]
object Chain {
def unapply[A,B,C](src : Chain[A,B,C]) : Option[(Elem[A,B], Elem[B,C])] =
Some( (src.head, src.tail) )
def apply[A,B,C](head : Elem[A,B], tail : Elem[B,C]) : Chain[A,B,C] =
new Chain(head, tail)
}在这个错误消息之后,信息变得更加丰富,显然是关于如何修复它。
然而,我不知道为什么它适用于case类。抱歉的。
工作示例如下:
trait Elem[+X, +Y] {
def ::[Z, XX >: X](other : Elem[Z, XX]) : Elem[Z, Y] = other match {
case Chain(head, tail) => Chain(head, tail :: this)
case simple => Chain(simple, this)
}
}
final class Chain[A, B, C](val head : Elem[A, B], val tail : Elem[B, C]) extends Elem[A, C]
object Chain {
def unapply[A,B,C](src : Chain[A,B,C]) : Option[(Elem[A,B], Elem[B,C])] =
Some( (src.head, src.tail) )
def apply[A,B,C](head : Elem[A,B], tail : Elem[B,C]) : Chain[A,B,C] =
new Chain(head, tail)
}编辑:
最终我发现:
case class A[T](a: T)
List(A(1), A("a")).collect { case A(x) => A(x) }
// res0: List[A[_ >: String with Int]] = List(A(1), A(a))
class B[T](val b: T)
object B {
def unapply[T](b: B[T]): Option[T] = Option(b.b)
}
List(new B(1), new B("b")).collect { case B(x) => new B(x) }
// res1: List[B[Any]] = List(B@1ee4afee, B@22eaba0c)很明显,它是编译器的特性。所以我认为没有办法重现整个case类的行为。
https://stackoverflow.com/questions/39004088
复制相似问题