2

Possible Duplicate:
Scala and forward references

Is there any rationale why the following works in Scala:

Version 1

object Strange extends App {
  val x = 42
  Console.println(x) // => outputs "42", as expected
}

Version 2

object Strange extends App {
  Console.println(x) // => "0" ?!
  val x = 42
}

Why does it compile at all and why behaves so weird without any warnings or whatsoever?

It's also the same issue with class:

class StrangeClass {
  Console.println(x) // => still "0"
  val x = 42
}

object TestApp extends App {
  new StrangeClass()
}

There are no such issue with regular method's body:

def nonStrangeMethod {
  Console.println(y) // => fails with "not found: value y", as expected
  y = 42
}

And behavior changes dramatically if we'd add "final" to val declaration:

class StrangeClass {
  Console.println(x) // => "42", but at least that's expected
  final val x = 42
}

For records, the following Java static (Scala's object) counterpart:

public class Strange {
    static {
        System.out.println(x);
    }
    static int x = 42;

    public static void main(String[] args) {}
}

fails compilation with plain & understandable error "Cannot reference a field before it is defined" on line #3 and Java non-static (Scala's class) counterpart:

public class Strange {
    Strange() {
        System.out.println(x);
        int x = 42;
    }

    public static void main(String[] args) {
        new Strange();
    }
}

obviously fails with "x cannot be resolved to a variable" on line #3.

Community
  • 1
  • 1
GreyCat
  • 16,622
  • 18
  • 74
  • 112

2 Answers2

3

It's because of the App trait which uses delayed initialization. From Programming in Scala regarding the App trait:

The code between the curly braces is collected into a primary constructor of the singleton object, and is executed when the class is initialized

The 2.9.0 release notes this too:

Objects inheriting the App trait instead make use of Scala 2.9’s delayed initialization feature to execute the whole body as part of an inherited main method.

So Console.println(x) is not executed until Strange is ran where you get this as output:

scala> s.main(Array[String]())
0

If you add another Console.println(x) after val x = 42 then it prints out:

scala> s.main(Array[String]())
0
42

The compiler knows that x exists in the current scope but it delays evaluation of it until it is executed and then it prints out the default value for Int which is 0.

Brian
  • 20,195
  • 6
  • 34
  • 55
  • Both `App` trait and `object` is not the issue here. I can replicate exactly the same behavior with a constructor of `class` without any traits. So the behavior you're specifying seems to refer to any constructor-like code. – GreyCat Nov 22 '12 at 03:19
2

The answer is essentially how that compiler constructs things. Since there are no static variables in Scala - the initialization basically happens in the constructor, so the Java equivalent of your second example would be similar to:

public class Strange {
    int x = 0;

    public Strange() {
        System.out.println(x);
        x = 42;
    }
}

Which would compile fine. I assume the compiler sets the value of the uninitialized int to 0 to avoid a NPE. If you reversed the order of the statements in the constructor, you will get the behavior you were describing in the first example.

There is some more detail in this question: Scala and forward references

Community
  • 1
  • 1
jcern
  • 7,798
  • 4
  • 39
  • 47
  • That doesn't really answer *why* it is so. Such behavior breaks lots of expectations (expectation of compilation error in case of missing variable name, `val`s changing its values in runtime, contrary to constant value contract, code behaving the same in constructors and ordinary methods) and I don't see any good it brings to the world. May be there *is* something I'm missing why it's necessary to process it this way? – GreyCat Nov 22 '12 at 04:29
  • Since objects don't use static values, they can be overridden in subclasses and otherwise enjoy all of the benefits of OO design. Because of that, the object needs to be allocated in memory and then its value is initialized by the proper constructor as opposed to a static value which can not be overridden and can be set directly. I may be off a bit as I've never really delved into compiler design, but I believe that is the gist of why. I can't speak much about the expectations though. – jcern Nov 22 '12 at 17:43