在 match case 中使用模式联编程序进行类型推断无法按预期工作

Type inference with pattern binder at sing @ in match case doesn't work as expected

假设 Lofty 是一个密封特征,Earthy 是它的一种情况 类。在这样的比赛中:

loftyItem match {
...
 case e @ Earthy(x,y,z) => { foo(e) }
...
}

其中 foo 需要一个 Earthy 作为参数,编译器崩溃了,因为 e 仅被推断为 Lofty 类型。我可以解决这个问题,但它不符合我认为事情应该如何发展的模型。 (我正在使用 Scala 2.13.5。)有人可以解释为什么编译器的行为有意义,让我再次对 Scala 感到满意吗?

回复评论,我说得更准确一点:

object QTest {
  trait Ethereal

  case class Lofty[A <: Ethereal](val slotMaybe: Option[A]) {
  }

  class Earthy(val field: Int) extends Ethereal

  object Earthy {
    def apply(fx: Int): Earthy = {
      new Earthy(fx)
    }

    def unapply(x: Ethereal): Option[Int] = x match {
      case y: Earthy => Some(y.field)
      case _ => None
    }
  }

  def testReally(just: Lofty[Ethereal]):
      Lofty[Earthy] = {
    just.slotMaybe match {
      case Some(c) => c match {
        case cnfC @ Earthy(i) => {
          just.copy(slotMaybe = Some(cnfC))
        }
        case _ => throw new RuntimeException("oops")
      }
      case _ => throw new RuntimeException("oops")
    }
  }
}

编译产生错误:

QTest.scala:25: error: type mismatch;
 found   : QTest.Ethereal
 required: QTest.Earthy
          just.copy(slotMaybe = Some(cnfC))

我显然是仓促下了结论,但完整的例子似乎也有同样的问题。为什么编译器为 cnfC 而不是 Earthy 推断类型 Ethereal?即使编译器为 @ 的大多数用途获得了正确的类型,为什么它在这里出错了?

SLS 8.1.3 Pattern Binders 状态

A pattern p implies a type T if the pattern matches only values of the type T.

中的模式 Earthy(i)
case cnfC @ Earthy(i) =>

代表extractor pattern意思是会根据你定义的unapply匹配,也就是

object Earthy {
  def unapply(x: Ethereal): Option[Int] = ???
}

因为 x 的声明类型更宽 Ethereal 而不是更窄 Earthy 它不会匹配

... only values of the type T

where T = Earthy,但它也可以匹配 Ethereal 的其他子类型。因此编译器只能确定它将是 some Ethereal.

如果你想让它用提取器模式编译,那么要么将你的 unapply 声明为

object Earthy {
  def unapply(x: Earthy): Option[Int] = ???
}

或更好的是使用案例 class 而不是自动获得正确的 unapply

case class Earthy(field: Int) extends Ethereal