I've got a large number of simple command-line Scala apps which share quite a bit of common structure. All of them inherit from scala.App, which is just fine. I would like to refactor out the shared structure of these command-line Apps into a common trait, which I could then inherit into my (much simpler) command-line app classes. The problem arises in that some of the common structure includes parsing of command-line arguments.
object MyScript extends BaseScript with App{
//small bits of business logic using components defined in BaseScript
}
trait BaseScript extends App{
val configuration = loadConfiguration(args(0))
//setup a bezillion components, usable from any of the scripts, based on the configuration
}
This compiles, but fails with an NPE when it comes time to actually dereference args
, presumably because the App
trait hasn't yet been initialized. Changing trait orders and changing the inheritance of App in BaseScript to be a self-type declaration do nothing, as have experiments with DelayedInit. Declaring the components as "lazy" in BaseScript would work, but I also wish need to actually use those components during initialization (e.g., setting up log directories and loading JDBC driver classes based on the configuration), so the benefits of laziness are lost. Is there something I can do to get the command-line arguments visible and initialized in the BaseScript trait?
I think your best bet is to change your BaseScript
trait into a class for two reasons. The first is that compared with classes, trait initializations are executed in reverse order. See this question on initialization behavior. Second, BaseScript
semantically is more of a superclass than additional behavior. I think you will find that this can simplify things.
When executing MyScript
, the following code initializes the BaseScript
class first. BaseScript
is dependent on the App
trait in turn and forces it to initialize first.
object MyScript extends BaseScript {
//small bits of business logic using components defined in BaseScript
println( "running" )
println( "arg(0): " + configuration )
}
class BaseScript extends App {
val configuration = loadConfiguration(args)
//setup a bezillion components, usable from any of the scripts, based on the configuration
def loadConfiguration( args: Array[String] ) = {
println( "configuring" )
if ( args.length > 0 ) args(0) else null
}
}
Have you tried using lazy val (and not extending the App
trait)?
trait BaseScript { self : App =>
lazy val configuration = loadConfiguration(args(0))
//setup a bezillion components, usable from any of the scripts
//based on the configuration
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With