Почему эти неявные преобразования привели к зацикливанию кода - PullRequest
7 голосов
/ 13 января 2012

Рассмотрим следующий код в Scala:

object Test {
  class A {}

  class B extends A {}

  class AI extends A {
    def sayHello: String = "Hello from AI"
  }

  implicit def AtoAI(a: A): AI = a

  class BI extends B {
    def sayHello: String = "Hello from BI"
  }

  implicit def BtoBI(b: B): BI = b

  def main(args: Array[String]) {
    val a = new A
    println(a.sayHello)

    val b = new B
    println(b.sayHello)
  }
}

Использование имплицитов приводит к зацикливанию кода.На самом деле, дизассемблирование показывает, что сгенерированные методы преобразования имеют только goto 0 внутри:

public Test$AI AtoAI(Test$A);
  Code:
   0:   goto    0

public Test$BI BtoBI(Test$B);
  Code:
   0:   goto    0

Что вызывает такое поведение?Я понимаю, что иерархия классов здесь является подозрительной, но неявное преобразование должно применяться только один раз.

Я использую Scala 2.9.1

Ответы [ 2 ]

12 голосов
/ 13 января 2012

Не приятно, но я бы точно не назвал это ошибкой.

сводится к

class A

class B

implicit def aToB(a: A) : B = a

Нет необходимости, чтобы обе стороны преобразования были связаны каким-либо образом. Неявное - это то же самое, что писать

implicit def aToB(a: A): B = aToB(a)

потому что компилятор вставляет вызов aToB для преобразования результата a в требуемый тип возвращаемого значения B.

Реализация goto 0 - это просто оптимизация хвостового вызова. Компилятор, вероятно, может выдать предупреждение, когда генерирует метод, который запускается таким образом.

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

implicit def listAToListB(l: list[A] = l match {
  case Nil => Nil
  case x:xs => toB(x) :: xs // equivalent to toB(x) :: listAToList[B](xs)
}

(хорошо, это всего лишь map(toB)). В любом случае то же самое может произойти с двумя взаимно рекурсивными последствиями. На мой взгляд, не стоит изменять спецификацию, чтобы избежать какой-либо возможности написать бесконечный цикл, ничего не делать, среди многих других. Но было бы неплохо, если бы такой цикл обнаруживался независимо от последствий.

2 голосов
/ 13 января 2012

Я не могу понять, почему код компилируется вообще. Я проверил, что он компилируется. Разве вам не пришлось бы опускаться внутри метода неявного преобразования?

Дидье отмечает, что неявное преобразование применяется рекурсивно, что означает, что код может компилироваться без снижения.

Следующий код добавляет downcast (который должен не изменять поведение во время выполнения). Это терпит неудачу во время выполнения с неудачными бросками. Так что для меня это выглядит как ошибка компилятора. Как говорит Дидье, явное понижение предотвращает рекурсивное применение неявного преобразования здесь.

object Test {
  class A {}

  class B extends A {}

  class AI extends A {
    def sayHello: String = "Hello from AI"
  }

  implicit def AtoAI(a: A): AI = a.asInstanceOf[AI]

  class BI extends B {
    def sayHello: String = "Hello from BI"
  }

  implicit def BtoBI(b: B): BI = b.asInstanceOf[BI]

  def main(args: Array[String]) {
    val a = new A
    println(a.sayHello)

    val b = new B
    println(b.sayHello)
  }
}

Завершая ответ на вопрос: вы вызываете метод AI для объекта класса A, который не имеет этого метода. Очевидно, это не может работать. Что происходит, не уточняется; в вашем случае это был бесконечный цикл.

...