17

最近我偶然发现了一个奇怪的(对我来说)编译器错误消息。考虑以下代码:

trait Foo {
  type Res <: Foo
  type Bar[X <: Res]
}

class MyFoo extends Foo {
  override type Res = MyFoo
  override type Bar[X <: Res] = List[X]
}

type FOO[F <: Foo, R <: Foo, B[_ <: R]] = F { type Res = R; 
                                              type Bar[X <: R] = B[X] }

def process[F <: Foo, R <: Foo, B[_ <: R]](f: FOO[F, R, B]) {}

现在,如果我想调用该process方法,我必须显式地编写类型参数:

process[MyFoo, MyFoo, List](new MyFoo) // fine

如果我写:

process(new MyFoo)

或者

process((new MyFoo): FOO[MyFoo, MyFoo, List])

我收到以下错误消息:

类型参数的推断类型 (MyFoo,MyFoo,List[X]) 不符合类型参数的预期类型 (类型 F,类型 R,类型 B)。List[X] 的类型参数与类型 B 的预期参数不匹配:类 List 有一个类型参数,但类型 B 有一个

为什么编译器不能推断类型(尽管我在调用参数中明确说明了它们)?那是什么class List has one type parameter, but type B has one意思?某物有一个,但另一个有一个,这就是为什么它们不适合在一起???

4

2 回答 2

3

如果我们查看 Scala 编译器,源代码可以帮助我们理解问题所在。我从未为 Scala 编译器做出过贡献,但我发现源代码非常易读,并且我已经对此进行了调查。

负责类型推断的类scala.tools.nsctypechecker.Infer可以通过在 Scala 编译器源代码中查找部分错误来找到。您会发现以下片段:

  /** error if arguments not within bounds. */
    def checkBounds(pos: Position, pre: Type, owner: Symbol,
                    tparams: List[Symbol], targs: List[Type], prefix: String) = {
      //@M validate variances & bounds of targs wrt variances & bounds of tparams
      //@M TODO: better place to check this?
      //@M TODO: errors for getters & setters are reported separately
      val kindErrors = checkKindBounds(tparams, targs, pre, owner)

      if(!kindErrors.isEmpty) {
        error(pos,
          prefix + "kinds of the type arguments " + targs.mkString("(", ",", ")") +
          " do not conform to the expected kinds of the type parameters "+ tparams.mkString("(", ",", ")") + tparams.head.locationString+ "." +
          kindErrors.toList.mkString("\n", ", ", ""))
      } 

所以现在的重点是理解为什么checkKindBounds(tparams, targs, pre, owner)返回这些错误。如果你沿着方法调用链往下走,你会看到 checkKindBounds 调用了另一个方法

val errors = checkKindBounds0(tparams, targs, pre, owner, true)

您会看到问题与检查更高类型的边界有关,在第 5784 行,在 checkKindBoundsHK 内:

 if (!sameLength(hkargs, hkparams)) {
        if (arg == AnyClass || arg == NothingClass) (Nil, Nil, Nil) // Any and Nothing are kind-overloaded
        else {error = true; (List((arg, param)), Nil, Nil) } // shortcut: always set error, whether explainTypesOrNot
      }

测试没有通过,看来在我的调试器中:

hkargs$1 = {scala.collection.immutable.Nil$@2541}"List()"
arg$1 = {scala.tools.nsc.symtab.Symbols$ClassSymbol@2689}"class List"
param$1 = {scala.tools.nsc.symtab.Symbols$TypeSymbol@2557}"type B"
paramowner$1 = {scala.tools.nsc.symtab.Symbols$MethodSymbol@2692}"method process"
underHKParams$1 = {scala.collection.immutable.$colon$colon@2688}"List(type R)"
withHKArgs$1 = {scala.collection.immutable.Nil$@2541}"List()"
exceptionResult12 = null
hkparams$1 = {scala.collection.immutable.$colon$colon@2688}"List(type R)"

所以看起来有一个更高种类的参数,类型 R,但没有提供值。

如果您实际上返回到 checkKindBounds,您会在代码段之后看到:

 val (arityMismatches, varianceMismatches, stricterBounds) = (
        // NOTE: *not* targ.typeSymbol, which normalizes
        checkKindBoundsHK(tparamsHO, targ.typeSymbolDirect, tparam, tparam.owner, tparam.typeParams, tparamsHO)
      )

包含arityMismatches一个元组List,B。现在您还可以看到错误消息是错误的:

类型参数的推断类型 (MyFoo,MyFoo,List[X]) 不符合类型参数的预期类型 (类型 F,类型 R,类型 B)。List[X] 的类型参数与类型 B 的预期参数不匹配:类 List 有一个类型参数,但类型 B有零

实际上,如果您在以下调用的第 5859 行设置断点

checkKindBoundsHK(tparamsHO, targ.typeSymbolDirect, tparam, tparam.owner, tparam.typeParams, tparamsHO)

你可以看到

tparam = {scala.tools.nsc.symtab.Symbols$TypeSymbol@2472}"type B"
targ = {scala.tools.nsc.symtab.Types$UniqueTypeRef@2473}"List[X]"

结论:

出于某种原因,在处理复杂的高级类型(例如您的类型)时,Scala 编译器推断是有限的。我不知道它是从哪里来的,也许你想向编译团队发送一个错误

于 2012-10-04T13:01:42.810 回答
0

我对 Scala 中类型推断器的确切工作原理只有一个模糊的理解,所以认为这个想法不是明确的答案。

  1. 类型推断在一次推断不止一种类型方面存在问题。

  2. 您在 FOO 的定义中使用了存在类型,它转换为:存在这样的类型,不确定这是否与 MyFoo 中给出的特定类型兼容

于 2012-10-04T08:37:17.957 回答