模式匹配中方法类型推断与class类型参数的区别

Difference between type inference of method and class type parameters in pattern matching

当类型参数来自封闭方法而不是封闭 class 时,为什么模式匹配的工作方式不同?例如,

trait Base[T]
case class Derived(v: Int) extends Base[Int]

class Test[A] {
  def method(arg: Base[A]) = {
    arg match {
      case Derived(_) => 42
    }
  }
}

给出错误

constructor cannot be instantiated to expected type;
 found   : A$A87.this.Derived
 required: A$A87.this.Base[A]
      case Derived(_) => 42
           ^

A是方法类型参数时编译成功

class Test {
  def method[A](arg: Base[A]) = {
    arg match {
      case Derived(_) => 42
    }
  }
}

这个问题是基于 Daniel 的 analysis, which I used to attempt to provide answer to similar question.

我没有 100% 完整的答案,但我有一个可能对你足够的指导。

Scala 编译器以一种非常特殊的方式处理 GADT(广义代数数据类型)。有的案件通过特殊处理解决了,有的案件悬而未决。 Dotty 正在尝试填补大部分漏洞,并且已经有 solved a lot of related issues, however there are still quite a few open 个。

Scala 2 编译器中特殊 GADT 处理的典型示例与您的用例非常相关。如果我们看一下:

def method[A](arg: Base[A]) = {
  arg match {
    case Derived(_) => 42
  }
}

并且我们明确声明 return 类型为 A:

def method[A](arg: Base[A]): A 

它会编译得很好。您的 IDE 可能会抱怨,但编译器会让它通过。方法说它 return 是一个 A,但模式匹配案例评估为 Int,理论上不应该编译。然而,在编译器中对 GADT 的特殊处理表明它很好,因为在那个特定的模式匹配分支中 A 已经 "fixed" 成为 Int (因为我们在 Derived 上匹配这是一个 Base[Int]).

GADT 的通用类型参数(在我们的例子中 A)必须在某处声明。这是有趣的部分 - 特殊编译器处理 仅在声明为封闭方法 的类型参数时才起作用。如果它来自封闭 trait/class 的类型成员或类型参数,它不会编译,正如您亲眼所见。

这就是为什么我说这不是一个 100% 完整的答案 - 我无法指出一个具体的地方(比如官方规范)来正确记录这个问题。关于在 Scala 中处理 GADT 的资源归结为 couple of blogposts, which are great by the way, but if you want more than that you will have to dig into the compiler code yourself. I tried doing exactly that, and I think it comes down to this method,但如果你真的想更深入,你可能想联系对 Scala 编译器代码库更有经验的人。