31

Consider this snippet:

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

As part of a larger program, this would lead to a failure at runtime. The compiler apparently permits the forward reference from 'b' to (uninitialized) 'c' but 'b' is left with c's original null value. Why is this permitted? Are there programming scenarios that would benefit from this feature?

Change the code to a straight sequence and the behavior changes:

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

Why is the behavior different? And why does this even work? Thanks.

Update 1:

The question came up how I ran the second example. I simplified the setup a bit and compiled it using Scala 2.9.0.1 inside IntelliJ IDEA 10.5.2 with the latest Scala plugin. Here is the exact code, in a freshly created and otherwise empty project, which I am using to test this, which compiles and runs fine in this environment:

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

For what it's worth, IDEA also thinks (when I click "through" the reference to 'c' in val b = c) that I am referring to the (later) declaration of 'c'.

Nathaniel Ford
  • 20,545
  • 20
  • 91
  • 102
Gregor Scheidt
  • 3,952
  • 4
  • 24
  • 28
  • I don't understand your second example. How do you run it? – thoredge Oct 14 '11 at 06:20
  • @thoredge Sorry for not clarifying that. I implemented it as the body of a method in an otherwise empty class and executed it as a unit test (using the TestNG unit test framework inside IntelliJ IDEA). – Gregor Scheidt Oct 15 '11 at 07:11
  • The last example now yields compiler error `forward reference extends over definition of value b` (scala-2.10.0-M6) – incrop Aug 30 '12 at 06:34
  • The last example prints `null` using 2.9.2 or 2.10.0, extending `App` rather than defining `main()` and running it with `sbt run` from the command line. – Adam Mackler Dec 27 '13 at 01:01

2 Answers2

21

The body of a class or an object is the primary constructor. A constructor, like a method, is a sequence of statements that are executed in order -- to do anything else, it would have to be a very different language. I'm pretty sure you wouldn't like for Scala to execute the statements of your methods in any other order than sequential.

The problem here is that the body of classes and objects are also the declaration of members, and this is the source of your confusion. You see val declarations as being precisely that: a declarative form of programming, like a Prolog program or an XML configuration file. But they are really two things:

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

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

Another part of your problem is that your example is very simple. It is a special case in which a certain behavior seems to make sense. But consider something like:

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)

What do you expect to happen? The semantics of constructor execution guarantees two things:

  1. Predictability. You might find it non-intuitive at first, but the rules are clear and relatively easy to follow.
  2. Subclasses can depend on superclasses having initialized themselves (and, therefore, its methods being available).

Output:

it will print "foobar" twice for more => https://docs.scala-lang.org/tutorials/FAQ/initialization-order.html

AZ_
  • 21,688
  • 25
  • 143
  • 191
Daniel C. Sobral
  • 295,120
  • 86
  • 501
  • 681
  • You say vals are effectively declarative programming, but if I set a val equal to a var, it's no longer declarative because the var is mutable. Isn't it just because val to val assignment is by reference/pointer rather than by value? – Phil H Oct 14 '11 at 06:49
  • I'm probably missing the point, but is `{ override val c = "foobar" }` an anonymous class here? If so, are you allowed to extend multiple classes if they're anonymous? Why doesn't `class C extends B with { override val c = "foobar" }` compile? – Luigi Plinge Oct 14 '11 at 17:21
  • @Phil What is declarative is saying the object has such and such vals, vars and defs. The initialization is not declarative. – Daniel C. Sobral Oct 14 '11 at 22:02
  • 2
    @Luigi No, that is early initialization syntax. – Daniel C. Sobral Oct 14 '11 at 22:03
  • @Daniel Don't get me wrong, I am not complaining - I'm simply trying to learn. My question "Are there programming scenarios that would benefit from this feature?" was in earnest, not to be snarky. I now see that there are scenarios in which case referencing a symbol not yet locally declared makes sense, among other things because of the uniform access principle that makes a vale and a def indistinguishable, if I understand correctly. – Gregor Scheidt Oct 15 '11 at 07:24
  • @Daniel Nevertheless, the first example clearly represents a programming error (paste some vals too far down in an object during refactoring…) that leads to failure much later and, whenever possible, I would like a compiler error or at least a warning: it's an object not a trait; there is no inherited 'c'; and I certainly don't want null. For the second example: this is just inside a method body - I still don't understand why it works. – Gregor Scheidt Oct 15 '11 at 07:25
  • @Gregor Please provide a complete second example. I certainly can't get it to compile as a method -- forward references are not allowed on those. I suspect you had `c` defined in an outer scope and didn't notice. – Daniel C. Sobral Oct 15 '11 at 22:24
  • @Gregor I can't get that to compile. – Daniel C. Sobral Oct 17 '11 at 21:26
  • @Daniel Hm. If YOU can't get it to compile I must be doing something wrong, I just can't figure out what. I went back and created a fresh project from scratch, with only the few lines of code in it that are shown in the now (again simplified, no more TestNG) update on the question. It still compiles fine and behaves as described. What can we do to track this down? Should I just file a ticket? Sorry to be wasting your time with this. – Gregor Scheidt Oct 18 '11 at 08:27
  • 2
    @Gregor Aha, this was a regression on Scala 2.9.0. It gives an error in both Scala 2.8.1 and 2.9.1. Ticket [SI-4419](https://issues.scala-lang.org/browse/SI-4419). – Daniel C. Sobral Oct 18 '11 at 14:35
1

It is because of outdated version of Scala.

With Scala 2.11.5 it compiled with warning or doesn't compiled at all:

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) }
                        ^
Andriy Plokhotnyuk
  • 7,883
  • 2
  • 44
  • 68
  • if you want to have your Scala "WTF" monent https://docs.scala-lang.org/tutorials/FAQ/initialization-order.html ;-) – AZ_ May 22 '18 at 08:37