infogrind infogrind - 1 month ago 9
Scala Question

Idiomatic way to use command-line parameters in Scala script

I would like to create a generic Scala script template (using Conscript). The script should take command-line options, for example a flag "-v" to enable verbose mode.

In say a Perl script, I would create a global variable $verbose, which I would set to true or false (or rather 1 or 0) depending if the flag is present. I understand that setting global variables in a script is not the Scala way.

Consider the following example:

object Main {
def main(args: Array[String]) {
val options = parseOptions(args); // some way of parsing

// how to use 'options' in the remainder of the script?
}
}


How to do it? One way I could see is to keep the option value as context variables in a closure, i.e., access them from nested functions. However, the problem with using nested functions is that I can't write unit tests for them. Another straightforward way would be to pass the options along with every function call, but that seems unwieldy.

Any suggestions?

Answer

For parsing options, you can use https://github.com/scopt/scopt, but I see that you're actually asking how to use a Config object that an options parser might return.

I think you need to pass the Config object around; it is a dependency of the code which uses it and code ought to have its dependencies provided directly where possible, rather than via global variables, as you note.

If you want to save some typing, you can pass it implicitly, i.e.

case class Config(verbose: Boolean)

object Main {
  def main(args: Array[String]) {
    implicit val config: Config = parseOptions(args); // some way of parsing

    new FooComponent().doSomeStuff()
  }
}

class FooComponent()(implicit config: Config) {

  def doSomeStuff() {
    ... stuff
    if (config.verbose) {
      println(...)
    }
    ...
  }
}

You also can use the IoC pattern so that separate modules don't depend on Main.Config, but instead expose an interface declaring what config they need that Config will implement.

i.e.

case class Config(verbose: Boolean) extends FooHandler.Config {
  def printFooInfo: Boolean = verbose
}

object Main {
  def main(args: Array[String]) {
    val config: Config = parseOptions(args);  // some way of parsing

    FooHandler.handleFoo(config)
  }
}

// This looks contrived with small code, but works well if
// your codebase is large and FooHandler is far away from Main
object FooHandler {
  trait Config {
    def printFooInfo: Boolean
  }

  def handleFoo(config: Config) {
    ... stuff
    if (config.printFooInfo) {
      println(...)
    }
    ...
  }
}
Comments