Dici
Dici

Reputation: 25950

Scala fails to initialize a val

I have found kind of a weirdness in the following Scala program (sorry to include all the code, but you'll see why I added it all) :

object md2html extends App {
    private val DEFAULT_THEME = Themes.AMAZON_LIGHT

    private val VALID_OPTIONS = Set("editorTheme", "logo", "style")
    try {
        // some code 1
    } catch {
        case t: Throwable => t.printStackTrace(); exitWithError(t.getMessage)
    }

    // some code 2 (method definitions only)

    private def parseOption(key: String, value: String) = {
        println(key + " " + VALID_OPTIONS)
        if (! Set("theme","editorTheme", "logo", "style").contains(key)) exitWithError(s"$key is not a valid option")   
        if (key == "theme") Themes(value).toMap else Map(key.drop(2) -> value)
    }

    // some code 3 (method definitions only)
}

If VALID_OPTIONS is defined after one of the some code..., it is evaluated to null in parseOption. I can see no good reason for that. I truncated the code for clarity, but if some more code is required I'll be happy to add it.

EDIT : I looked a bit more into it, and here is what I found.

When extending App, the val is not initialized with this code

object Test extends App {
    printTest()
    def printTest = println(test)
    val test = "test"
}

With a regular main method, it works fine :

object Test {
    def main(args: Array[String]): Unit = {
      printTest
    }
    def printTest = println(test)
    val test = "test"
}

Upvotes: 7

Views: 1113

Answers (2)

0__
0__

Reputation: 67280

I had overseen that you use extends App. This is another pitfall in Scala, unfortunately:

object Foo extends App {
  val bar = "bar"
}

Foo.bar            // null!
Foo.main(Array())
Foo.bar            // now initialized

The App trait defers the object's initialization to the invocation of the main method, so all the vals are null until the main method has been called.

In summary, the App trait and vals do not mix well. I have fallen into that trap many times. If you use App, avoid vals, if you have to use global state, use lazy vals instead.

Upvotes: 15

0__
0__

Reputation: 67280

Constructor bodies, and this goes for singleton objects as well, are evaluated strictly top to bottom. This is a common pitfall in Scala, unfortunately, as it becomes relevant where the vals are defined if they are referenced in other places of the constructor.

object Foo {
  val rab = useBar    // oops, involuntarily referring to uninitialized val 
  val bar = "bar"

  def useBar: String = bar.reverse        
}

Foo   // NPE

Of course, in a better world, the Scala compiler would either disallow the above code, re-order the initialization, or at least warn you. But it doesn't...

Upvotes: 2

Related Questions