In Scala; should I use the App trait?

Micheal Hill picture Micheal Hill · Jun 26, 2014 · Viewed 11.5k times · Source

I've just started learning Scala and many of the tutorials that I'm following are using a combination of different representations for a main method. Aside from the familiar main method; there's also the use of traits App or Application. It appears that Application has been deprecated and is not recommended, but I can't find any information that explains much beyond this about each of these ways to define an entry point.

So, I'm wondering if someone could explain to me:

  • How do the traits App and Application work?
  • Why is the trait Application no longer recommended and what does the App trait do that is different?
  • Where should I use the traditional main method and when should I use App to start my program? What's the difference between the two approaches?

Answer

Rafael Winterhalter picture Rafael Winterhalter · Jun 26, 2014

The problem with the Application trait is actually described in its documentation:

(1) Threaded code that references the object will block until static initialization is complete. However, because the entire execution of an object extending Application takes place during static initialization, concurrent code will always deadlock if it must synchronize with the enclosing object.

This is a tricky one. If you extend the Application trait, you are basically creating a Java class:

class MyApplication implements Application {
  static {
    // All code goes in here
  }
}

The JVM runs the above class initializer implicitly synchronized on the MyApplication class. This way, it is assured that no instance of MyApplication is created before its class is initialized. If you spawn a thread from your application that again needs to access an instance of MyApplication, your application would dead lock as the class initialization is only complete after the entire program has executed. This implies a paradox as no instance can be created as long as your program is running.

(2) As described above, there is no way to obtain the command-line arguments because all code in body of an object extending Application is run as part of the static initialization which occurs before Application's main method even begins execution.

A class initializer does not take any arguments. Also, it is run first, before any values could be handed to the class as the class initializer needs to be executed before you could even assign a static field value. Thus, the args that you normally receive on a main method are lost.

(3) Static initializers are run only once during program execution, and JVM authors usually assume their execution to be relatively short. Therefore, certain JVM configurations may become confused, or simply fail to optimize or JIT the code in the body of an object extending Application. This can lead to a significant performance degradation.

The JVM optimizes code that is run frequently. This way, it makes sure that no run time is wasted on methods that are not really a performance bottle neck. However, it safely assumes that static methods are only executed once as they cannot be invoked manually. Thus, it will not optimize the code that is run from a class initializer which is however your application's main method code if you are using the Application trait.

The App trait fixes all this by extending DelayedInit. This trait is explicitly known to the Scala compiler such that initialization code is not run from a class initializer but from another method. Note the for name reference that is haded to the trait's only method:

trait Helper extends DelayedInit {
  def delayedInit(body: => Unit) = {
    println("dummy text, printed before initialization of C")
    body
  }
}

When implementing DelayedInit, the Scala compiler wraps any initialization code of its implementing class or object into a for name function which is then passed to the delayedInit method. No initialization code is executed directly. This way, you can also run code before an initializer is run what allows Scala for example to print an application's runtime metrics to the console which is wrapped around the program's entry point and exit. However, there are some caveats of this approach and using DelayedInit is therefore deprecated. You should really only rely on the App trait which solves the problems that are imposed by the Application trait. You should not implement DelayedInit directly.

You can still define a main method if you want to, as long as you define it in an object. This is mostly a matter of style:

object HelloWorld {
  def main(args: Array[String]) {
    println("Hello, world!")
  }
}