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

Mar*_*lic 9 scala type-inference pattern-matching type-parameter

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

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

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

给出错误

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

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

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

这个问题基于丹尼尔的分析,我曾经试图为类似问题提供答案

slo*_*ouc 4

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

Scala 编译器以非常特殊的方式处理 GADT(广义代数数据类型)。有的案件通过特殊处理得到解决,有的案件没有解决。Dotty 正在尝试填补大部分漏洞,它已经解决了很多相关问题,但仍然存在不少未解决的问题。

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

def method[A](arg: Base[A]) = {
  arg match {
    case Derived(_) => 42
  }
}
Run Code Online (Sandbox Code Playgroud)

我们明确声明返回类型为A

def method[A](arg: Base[A]): A 
Run Code Online (Sandbox Code Playgroud)

它会编译得很好。您的 IDE 可能会抱怨,但编译器会让它通过。方法说它返回 an A,但模式匹配情况计算结果为 an Int,理论上不应编译。然而,编译器中对 GADT 的特殊处理表明这很好,因为在该特定模式匹配分支A已被“固定”为 an Int(因为我们匹配的Derived是 a Base[Int])。

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

这就是为什么我说这不是 100% 完整的答案 - 我无法指出正确记录这一点的具体位置(例如官方规范)。有关在 Scala 中处理 GADT 的资料可以归结为几篇 文,顺便说一句,这些 文都很棒,但如果您想要更多内容,则必须自己深入研究编译器代码。我尝试这样做,我认为这归结为这种方法,但如果你真的想更深入,你可能想找一些对 Scala 编译器代码库更有经验的人。