Почему я не могу определить переменную рекурсивно в блоке кода?

Почему я не могу определить переменную рекурсивно в кодовом блоке?

scala> {
     | val test: Stream[Int] = 1 #:: test
     | }
<console>:9: error: forward reference extends over definition of value test
              val test: Stream[Int] = 1 #:: test
                                            ^

scala> val test: Stream[Int] = 1 #:: test
test: Stream[Int] = Stream(1, ?)

lazy ключевое слово решает эту проблему, но я не могу понять, почему он работает без кода, но генерирует ошибку компиляции в блоке кода.

Ответ 1

Обратите внимание, что в REPL

scala> val something = "a value"

оценивается более или менее следующим образом:

object REPL$1 {
  val something = "a value"
}
import REPL$1._

Итак, любой val (или def и т.д.) является членом внутреннего вспомогательного объекта REPL.

Теперь дело в том, что классы (и объекты) допускают прямые ссылки на своих членов:

object ForwardTest {
  def x = y // val x would also compile but with a more confusing result
  val y = 2
}
ForwardTest.x == 2

Это не верно для val внутри блока. В блоке все должно быть определено в линейном порядке. Таким образом, val больше не являются членами, а просто переменными (или значениями, соответственно). Не компилируется также следующее:

def plainMethod = { // could as well be a simple block
  def x = y
  val y = 2
  x
}

<console>: error: forward reference extends over definition of value y
     def x = y
             ^

Это не рекурсия, которая делает разницу. Разница в том, что классы и объекты допускают прямые ссылки, а блоки - нет.

Ответ 2

Я добавлю, что когда вы пишете:

object O {
  val x = y
  val y = 0
}

Вы на самом деле пишете это:

object O {
  val x = this.y
  val y = 0
}

Это маленькое this - это то, чего не хватает, когда вы объявляете этот материал внутри определения.

Ответ 3

Причина такого поведения зависит от времени инициализации валидаров. Если вы введете val x = 5 непосредственно в REPL, x станет членом объекта, значения которого могут быть инициализированы значением по умолчанию (null, 0, 0.0, false). Напротив, значения в блоке не могут инициализироваться значениями по умолчанию.

Это имеет тенденцию к разному поведению:

scala> class X { val x = y+1; val y = 10 }
defined class X

scala> (new X).x
res17: Int = 1

scala> { val x = y+1; val y = 10; x } // compiles only with 2.9.0
res20: Int = 11

В Scala 2.10 последний пример больше не компилируется. В 2.9.0 значения переупорядочиваются компилятором для его компиляции. Существует отчет об ошибке который описывает различные времена инициализации.

Ответ 4

Я хотел бы добавить, что Scala Рабочий лист в Scala -IDE (v4.0.0) на основе Eclipse не ведет себя как REPL, как можно было бы ожидать (например, https://github.com/scala-ide/scala-worksheet/wiki/Getting-Started говорит, что "Рабочие листы похожи на сеанс REPL на стероидах" ) в этом отношении, а скорее как определение одного длинного метода: То есть, прямые ссылки на определения val (включая рекурсивные val) в листе должны быть сделаны членами какого-либо объекта или класса.