Подтвердить что ты не робот

Класс A имеет один параметр типа, но тип B имеет один

Недавно я наткнулся на странное (мне) сообщение об ошибке компилятора. Рассмотрим следующий код:

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). Параметры списка [X] не соответствуют ожидаемым параметрам типа B: класс List имеет один параметр типа, но тип B имеет один

Почему компилятор не может вывести типы (хотя я явно указал их при параметре вызова)? И что означает это class List has one type parameter, but type B has one? Что-то есть, но у другого есть и один, и поэтому они не подходят друг к другу.

4b9b3361

Ответ 1

Если мы посмотрим на компилятор 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 = {[email protected]}"List()"
arg$1 = {[email protected]}"class List"
param$1 = {[email protected]}"type B"
paramowner$1 = {[email protected]}"method process"
underHKParams$1 = {[email protected]}"List(type R)"
withHKArgs$1 = {[email protected]}"List()"
exceptionResult12 = null
hkparams$1 = {[email protected]}"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 содержит список кортежей, B. И теперь вы также можете увидеть, что сообщение об ошибке неверно:

выведенные типы аргументов типа (MyFoo, MyFoo, List [X]) не соответствуют ожидаемым типам параметров типа (тип F, тип R, тип B). Параметры списка [X] не соответствуют ожидаемому типу B parameters: class List имеет один параметр типа, но тип B имеет ZERO

Фактически, если вы поместите точку останова на строку 5859 при следующем вызове

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

вы можете видеть, что

tparam = {[email protected]}"type B"
targ = {[email protected]}"List[X]"

Заключение:

По какой-то причине при работе со сложными типами более высокого класса, такими как ваш, вывод Scala компилятора ограничен. Я не знаю, откуда она взялась, возможно, вы хотите отправить ошибку команде компилятора.

Ответ 2

У меня только смутное понимание точной работы типа inferrer в Scala, поэтому рассмотрим эти идеи не окончательные ответы.

  • Тип inferring имеет проблемы с выводом более одного типа сразу.

  • В определении FOO используется экзистенциальный тип, который преобразуется в: существует такой тип, не уверен, совместим ли он с конкретным типом, указанным в MyFoo