Объяснение нелегальной циклической ссылки, включающей имплициты

В моем проекте у меня есть тип A, используемый для аргументов в нескольких местах, где я хочу, чтобы несколько типов автоматически преобразовывались в этот тип. Я реализовал это, используя несколько неявных классов в сопутствующем объекте A. Я удалил все, что не могло вызвать проблемы:

trait A
object A {
  implicit class SeqA[T](v: Seq[T])(implicit x: T => A) extends A
  implicit class IntA(v: Int) extends A
  implicit class TupleA(v: (Int, Int)) extends SeqA(Seq(v._1, v._2))
}

Но scalac отклоняет этот код из-за недопустимой циклической ссылки:

$ scalac -version
Scala compiler version 2.12.8 -- Copyright 2002-2018, LAMP/EPFL and Lightbend, Inc.
$ scalac A.scala 
A.scala:5: error: illegal cyclic reference involving class TupleA
  implicit class TupleA(v: (Int, Int)) extends SeqA(Seq(v._1, v._2))
                 ^
one error found

В чем именно проблема? Что компилятор привязывает к выполнению / выводу / разрешению, содержащему недопустимый цилиндр?

Бонусные баллы за действительный способ реализации этих неявных классов.


person Feuermurmel    schedule 21.02.2019    source источник


Ответы (1)


Вы столкнулись с SI-9553, три с половиной года -старый баг компилятора.

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

trait A
object A {
  implicit class SeqA[T](v: Seq[T])(implicit x: T => A) extends A
  implicit class IntA(v: Int) extends A
  implicit class TupleA(v: (Int, Int)) extends SeqA[Int](Seq(v._1, v._2))
}

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

В качестве примечания: вы можете исследовать подобные проблемы с помощью параметра компилятора, например -Xprint:typer. В этом случае в REPL отображается следующее:

// ...
implicit class TupleA extends $line6.$read.$iw.$iw.A.SeqA[Int] {
  <paramaccessor> private[this] val v: (Int, Int) = _;
  def <init>(v: (Int, Int)): $line6.$read.$iw.$iw.A.TupleA = {
    TupleA.super.<init>(scala.collection.Seq.apply[Int](v._1, v._2))({
      ((v: Int) => A.this.IntA(v))
    });
    ()
  }
};
implicit <synthetic> def <TupleA: error>(v: (Int, Int)): <error> = new TupleA(v)

Что в данном случае не очень полезно, но, по крайней мере, указывает на то, что проблема возникает в определении метода синтетического преобразования для неявного класса TupleA, а не в какой-то момент до этого.

person Travis Brown    schedule 21.02.2019
comment
Большое тебе спасибо! Обладая этими знаниями, я смог исправить эту проблему в моем реальном коде. Я добавил параметр типа в SeqA после того, как у меня уже был TupleA, поэтому я не заметил, что там был выведен параметр типа. - person Feuermurmel; 21.02.2019