Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getting command-line arguments in Scala traits

Tags:

scala

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?

like image 256
Dave Griffith Avatar asked Feb 23 '23 05:02

Dave Griffith


2 Answers

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
  }
}
like image 57
Neil Essy Avatar answered Feb 25 '23 02:02

Neil Essy


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
}
like image 40
oxbow_lakes Avatar answered Feb 25 '23 03:02

oxbow_lakes