我尝试用普通类和伴侣对象替换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语句隐式创建的方法和为世俗类明确编写的方法有什么区别?
答案 0 :(得分:3)
这个答案最终比我预期的要长。如果您只想了解类型推断所发生的事情,请跳到最后。否则,您将获得解决问题的步骤。
case
,但不是case class
在这种情况下,尽管我不愿承认,案例类真的很神奇。特别是,他们在类型检查器级别获得特殊处理(我认为我们可以同意,如果它已经过了那个阶段你的代码就可以工作了 - 你甚至可以投入足够的强制转换来使其工作)。
问题是,令人惊讶的是,不是在类Chain
本身,而是在它使用的地方,特别是在模式匹配部分。例如,考虑案例类
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
进行编译,您将看到类型检查器看到的内容。案例类版本有
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)
}
因此类型检查器实际上获得了一个不同的(特殊)案例构造。
match
被翻译成什么?只是为了好玩,我们可以检查下一阶段-Xprint:patmat
会发生什么情况,这会扩展模式(尽管这些事实上这些不再是真正有效的Scala程序真的很痛苦)。首先,案例类有
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
方法!对于非案例类版本,我将使用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实际上并没有作弊 - 这种行为都在规范中。特别是,我们看到constructor patterns从哪个案例类中获益是特别的,因为除其他外,它们是irrefutable(与我上面提到的关于Scala不想使用{ {1}}方法,因为它“知道”它只是提取字段。)
真正感兴趣的部分是8.3.2 Type parameter inference for constructor patterns。常规类和案例类之间的区别在于unapply
模式是Chain
是案例类时的“构造函数模式”,否则只是常规模式。构造函数模式
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
输出中的类型中包含-Xprint:typer
。这会进行类型检查。
但是,构造函数模式特定于case类,所以没有 - 这里没有办法模仿case类行为。
构造函数模式的格式为
Chain[C,Any,A]
,其中c(p1,…,pn)
。它 由一个稳定的标识符n≥0
组成,后跟元素模式c
。构造函数p1,…,pn
是一个简单或限定的名称 表示c
。
答案 1 :(得分:0)
首先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)
}
在此错误消息变得更具信息性之后,显然如何修复此问题。
但是,我不知道为什么它适用于案例类。遗憾。
工作示例是:
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)
很明显它是编译器功能。所以我认为没有办法重现完整的案例类行为。