Question Forward References - pourquoi ce code compile-t-il?


Considérez cet extrait:

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

Dans le cadre d'un programme plus vaste, cela entraînerait une défaillance à l'exécution. Le compilateur autorise apparemment la référence en avant de 'b' à (non initialisé) 'c' mais 'b' reste avec la valeur nulle originale de c. Pourquoi est-ce permis? Existe-t-il des scénarios de programmation qui bénéficieraient de cette fonctionnalité?

Changez le code en une séquence droite et le comportement change:

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

Pourquoi le comportement est-il différent? Et pourquoi ça marche même? Merci.

Mise à jour 1:

La question a été posée comment j'ai couru le deuxième exemple. J'ai simplifié un peu la configuration et je l'ai compilée en utilisant Scala 2.9.0.1 dans IntelliJ IDEA 10.5.2 avec le dernier plug-in Scala. Voici le code exact, dans un projet fraîchement créé et vide, que j'utilise pour le tester, qui compile et fonctionne correctement dans cet environnement:

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

Pour ce que cela vaut, IDEA pense aussi (quand je clique "à travers" la référence à "c" dans val b = c) que je fais référence à la déclaration (plus tard) de "c".


25
2017-10-14 03:49


origine


Réponses:


Le corps d'une classe ou d'un objet est le constructeur principal. Un constructeur, comme une méthode, est une séquence d'instructions exécutées dans l'ordre: pour faire autre chose, le langage doit être très différent. Je suis sûr que vous ne voudriez pas que Scala exécute les instructions de vos méthodes dans un ordre autre que séquentiel.

Le problème ici est que le corps des classes et des objets est aussi la déclaration des membres, et c'est la source de votre confusion. Tu vois val déclarations comme étant précisément cela: une forme déclarative de programmation, comme un programme Prolog ou un fichier de configuration XML. Mais ce sont vraiment deux choses:

// This is the declarative part
object A {
  val b
  val c
}

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

Une autre partie de votre problème est que votre exemple est très simple. C'est un cas particulier dans lequel un certain comportement semble avoir un sens. Mais considérez quelque chose comme:

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'attendez-vous pour arriver? La sémantique de l'exécution des constructeurs garantit deux choses:

  1. Prévisibilité. Vous pourriez le trouver non intuitif au début, mais les règles sont claires et relativement faciles à suivre.
  2. Les sous-classes peuvent dépendre de l'initialisation des superclasses (et donc de la disponibilité de ses méthodes).

Sortie:

il imprimera "foobar" deux fois pour plus => https://docs.scala-lang.org/tutorials/FAQ/initialization-order.html


17
2017-10-14 04:31



C'est à cause de la version obsolète de Scala.

Avec Scala 2.11.5, il a été compilé avec un avertissement ou n’a pas été compilé du tout:

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) }
                        ^

1
2018-02-26 14:12