3

I was reading the sbt documentation, and I came across this example in the section on multi project builds:

import sbt._
import Keys._

object HelloBuild extends Build {
    lazy val root = Project(id = "hello",
                            base = file(".")) aggregate(foo, bar)

    lazy val foo = Project(id = "hello-foo",
                           base = file("foo"))

    lazy val bar = Project(id = "hello-bar",
                           base = file("bar"))
}

I am wondering how it is possible to reference the values foo and bar before they have been declared? I figure it has something to do with the lazy keyword, but from my reading, I thought the lazy keyword only delayed initialization? It seems here that the values are somehow in scope even before declaration, never mind initialization...

Hopefully someone is able to explain what is going on here!

Justin Lang
  • 591
  • 1
  • 3
  • 17
  • This is such a dupe. http://stackoverflow.com/q/7762838/1296806 http://stackoverflow.com/q/10257289/1296806 Hilarious quote from 2008: Clearly people don't seem to be paying attention to all the previous messages on this subject. I haven't looked at a recent draft of "Programming in Scala" but apparently it needs to have a discussion of initialization in 24pt bold text so people don't keep asking this over and over again. – som-snytt Aug 02 '13 at 07:07

2 Answers2

7

See chapter 4 of the Scala language specification:

The scope of a name introduced by a declaration or definition is the whole statement sequence containing the binding. However, there is a restriction on forward references in blocks: In a statement sequence s1 ... sn making up a block, if a simple name in si refers to an entity defined by sj where j ≥ i, then for all sk between and including si and sj,

  • sk cannot be a variable definition.
  • If sk is a value definition, it must be lazy.

In other words: you can have forward references on lazy vals, but only if there are no vars or non-lazy vals between them. This is kind of intuitive if you consider that a lazy val works more like a method than like a variable, and walk though two quick examples on the REPL:

scala> object o { val a = b; lazy val c = 6; lazy val b = c }
defined module o

scala> o.a
res1: Int = 6

In this first example, Scala evaluates a by invoking b, which in turn invokes c, which evaluates to 6. But in the next example, when c is not lazy ...

scala> object o { val a = b; val c = 6; lazy val b = c }
defined module o

scala> o.a
res2: Int = 0

When Scala evaluates a, it invokes b, which returns the current value of c (which at that time is 0, the JVM's default value for integers, because c is not yet initialized). Then c is initialized afterward, but by then it is too late.

See also: How are lazy val class variables implemented in Scala 2.10?

Community
  • 1
  • 1
Chris Martin
  • 30,334
  • 10
  • 78
  • 137
  • If you think in a way, this is very similar to Java class containing static variables and `lazy vals` as static method. It gives exactly the same behavior – Jatin Aug 02 '13 at 07:03
  • @Jatin Um, no. lazy vals are cached on an instance basis. Even if the instance is a singleton. – som-snytt Aug 02 '13 at 07:18
  • @som-snytt Yes obviously. I meant about the behavior and initialization – Jatin Aug 02 '13 at 07:18
2

Just like in Java, a class instance variable (or scala val) is "in scope" within any part of the containing class/object, regardless of where it is declared.

That said, it will not be "initialized" for any code referencing it and running prior to it's point of declaration. A lazy-val's code doesn't run at the point it's declared, so that's safe.

Richard Sitze
  • 8,262
  • 3
  • 36
  • 48