scala initialization lazy-evaluation

scala - Referencias a futuro: ¿por qué compila este código?



initialization lazy-evaluation (2)

Considera este fragmento:

object A { val b = c val c = "foo" } println( A.b ) // prints "null"

Como parte de un programa más grande, esto llevaría a una falla en el tiempo de ejecución. El compilador aparentemente permite la referencia directa de ''b'' a (sin inicializar) ''c'', pero ''b'' se queda con el valor nulo original de c. ¿Por qué esto está permitido? ¿Hay escenarios de programación que se beneficiarían de esta característica?

Cambie el código a una secuencia recta y el comportamiento cambia:

val b = c val c = "foo" println( b ) // prints "foo"

¿Por qué el comportamiento es diferente? ¿Y por qué esto incluso funciona? Gracias.

Actualización 1:

Surgió la pregunta de cómo ejecuté el segundo ejemplo. Simplifiqué un poco la configuración y la compilé usando Scala 2.9.0.1 dentro de IntelliJ IDEA 10.5.2 con el último complemento de Scala. Aquí está el código exacto, en un proyecto recién creado y vacío, que estoy usando para probar esto, que compila y funciona bien en este entorno:

package test object Main { def main( args: Array[String] ) { val b = c val c = "foo" println( b ) // prints "foo" } }

Por lo que vale, IDEA también piensa (cuando hago clic en "a través" de la referencia a "c" en val b = c) que me estoy refiriendo a la declaración (posterior) de "c".


El cuerpo de una clase o un objeto es el constructor primario. Un constructor, como un método, es una secuencia de declaraciones que se ejecutan en orden: para hacer cualquier otra cosa, debería ser un lenguaje muy diferente. Estoy bastante seguro de que no le gustaría que Scala ejecute las declaraciones de sus métodos en ningún otro orden que secuencialmente.

El problema aquí es que el cuerpo de las clases y los objetos también son la declaración de los miembros, y esta es la fuente de su confusión. Verá que las declaraciones val son precisamente eso: una forma declarativa de programación, como un programa Prolog o un archivo de configuración XML. Pero en realidad son dos cosas:

// This is the declarative part object A { val b val c } // This is the constructor part object A { b = c c = "foo" }

Otra parte de su problema es que su ejemplo es muy simple. Es un caso especial en el que cierto comportamiento parece tener sentido. Pero considera algo como:

abstract class A { def c: String } class B extends A { val b = c override val c = "foo" } class C extends { override val c = "foobar" } with B val x = new C println(x.b) println(x.c)

¿Qué esperas que pase? La semántica de la ejecución del constructor garantiza dos cosas:

  1. Previsibilidad Puede parecer que no es intuitivo al principio, pero las reglas son claras y relativamente fáciles de seguir.
  2. Las subclases pueden depender de que las superclases se hayan inicializado (y, por lo tanto, que sus métodos estén disponibles).

Salida:

imprimirá "foobar" dos veces para obtener más => https://docs.scala-lang.org/tutorials/FAQ/initialization-order.html


Es debido a la versión obsoleta de Scala.

Con Scala 2.11.5 compiló con advertencia o no compiló en absoluto:

C:/Users/Andriy/Projects/com/github/plokhotnyuk>scala Welcome to Scala version 2.11.5 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_31). Type in expressions to have them evaluated. Type :help for more information. scala> { object A { val b = c; val c = "foo" }; println(A.b) } <console>:9: warning: Reference to uninitialized value c { object A { val b = c; val c = "foo" }; println(A.b) } ^ null scala> { val b = c; val c = "foo"; println(A.b) } <console>:9: error: forward reference extends over definition of value b { val b = c; val c = "foo"; println(A.b) } ^