Почему нужно пересылать ссылочные значения внутри блоков в Scala, чтобы быть ленивыми?

Объем имени, введенного декларацией или определением, является целая последовательность операторов, содержащая привязку. Однако существует ограничение на прямые ссылки в блоках: в последовательности операторов s[1]...s[n] составляющий блок, если простое имя в s[i] относится к объект, определенный s[j], где j >= i, то для всех s[k]между и s[i] и s[j],

  • s[k] не может быть определением переменной.
  • Если s[k] - это определение значения, оно должно быть lazy.

Изменить: Я не уверен, что ответ Микаэля Майера на самом деле объяснил все. Рассмотрим:

object Test {
  def main(args: Array[String]) {
    println(x)
    lazy val x: Int = 6
  }
}

Здесь ленивое значение x обязательно должно быть прочитано/оценено до того, как оно будет определено в коде! Что противоречило бы Микаэлю, утверждают, что ленивая оценка устраняет необходимость оценивать вещи до их определения.

Ответ 1

Обычно вы не можете иметь это:

val e: Int = 2
val a: Int = b+c
val b: Int = c
val c: Int = 1
val d: Int = 0

поскольку значение c еще не определено во время определения a. Поскольку ссылки c, все значения между a и c должны быть ленивыми, чтобы избежать зависимости

val e: Int = 2
lazy val a: Int = b+c
lazy val b: Int = c
lazy val c: Int = 1
val d: Int = 0

Это фактически преобразует a, b и c как объекты, значение которых инициализируется при его чтении, которое было бы после объявления, то есть это было бы равнозначно:

val e: Int = 2
var a: LazyEval[Int] = null
var b: LazyEval[Int] = null
var c: LazyEval[Int] = null
a = new LazyEval[Int] {
  def evalInternal() = b.eval() + c.eval()
}
b = new LazyEval[Int] {
  def evalInternal() = c.eval()
}
c = new LazyEval[Int] {
  def evalInternal() = 1
}
val d = 0

где LazyEval будет выглядеть следующим образом (реализуется самим компилятором)

class LazyEval[T] {
  var value: T = _
  var computed: Boolean = false
  def evalInternal(): T // Abstract method to be overriden
  def eval(): T = {
     if(computed) value else {
       value = evalInternal()
       computed = true
       value
     }
  }
}

Edit

vals действительно не существуют в java. Они являются локальными переменными или не существуют при вычислении. Следовательно, декларация ленивого val существует прежде, чем что-либо будет сделано. И помните, что замыкания реализованы в Scala. Ваш блок будет переписан следующим образом:

  object Test {
    def main(args: Array[String]) {
      // Declare all variables, val, vars.
      var x: Lazy[Int] = null
      // No more variables to declare. Lazy/or not variable definitions
      x = new LazyEval[Int] {
        def evalInternal() = 6
      }
      // Now the code starts
      println(x)
    }
  }

Ответ 2

Вы пытаетесь избежать ссылок на сущности, которые предположительно неинициализированы (или которые, возможно, неинициализированы).

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

Например,

{ val a = b ; val b = 1 }  // if allowed, value of a is undefined

но в шаблоне

class X { val a = b ; val b = 1 }       // warning only
val x = new { override val b = 2 } with X
x.a  // this is 2
class Y(override val b: Int) extends X  // similarly

Вы также хотите избежать этого:

locally {
  def a = c
  val b = 2     // everything in-between must be lazy, too
  def c = b + 1 
}

Локальные объекты явно равны ленивым vals:

{ object p { val x = o.y } ; object o { val y = 1 } }

Другие виды прямой ссылки:

{ val x: X = 3 ; type X = Int }

Спецификация говорит о прямых ссылках на "сущности" - "имя относится к сущности" - что в других местах означает как термины, так и типы, но, очевидно, это действительно означает только термины здесь.

Это позволит вам нанести вред себе:

{ def a: Int = b ; def b: Int = a; a }

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