Почему нужно пересылать ссылочные значения внутри блоков в 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 }
Возможно, ваш способ самоуничтожения должен быть четко определен. Тогда это нормально.