Search code examples
scalaapache-sparkoptparse

Scala-Spark - Try/Catch is changing type of case class


I am trying to use the sellmerfud.optparse to parse the parameters used to launch the Spark2-Submit jar file. The code I am pasting below is adapted to paste into the spark2-shell where I force the args values as they would be passed via the command line.

The problem is that when I use the try/catch the variable I am setting does not get assigned to the class but when I do not use the try/catch, the args array parses fine and the values are available I am pasting what I captured in the spark2-shell session to provide information to see what is happening.

  import org.sellmerfud.optparse._
  case class Config(keyVal: String            = "tst",
                  startDate: String          = "",
                  endDate: String            = "")
  var args = Array("-k","testkey","-s","2018-01-01","-e","2018-03-31")
  var clsCfg = try {
    new OptionParser[Config] {
      banner = "testargs [options] file...\n"
      separator("")
      separator("Options: ")
      reqd[String]("-k <string>", "--keyval <string>", "Enter Key Value")
          { (v, cfg) => cfg.copy(keyVal = v) }
      optl[String]("-s", "--startdate <date>", "Enter date in mm-dd-yyyy format.")
          { (v, cfg) => cfg.copy(startDate = v.toString()) }
      optl[String]("-e", "--enddate <date>", "Enter date in mm-dd-yyyy format.")
          { (v, cfg) => cfg.copy(endDate = v.toString) }
    }.parse(args, Config())
  } catch { case e: OptionParserException => println(e.getMessage); java.lang.System.exit(1) }
  println("clsCfg: " + clsCfg)
  println("\nArguments passed as array, one array element per row:")
  println(args.deep.mkString("\n"))
  println("clscfg.keyVal: " + clsCfg.keyVal)

Lines output from above code:

import org.sellmerfud.optparse._
defined class Config
args: Array[String] = Array(-k, testkey, -s, 2018-01-01, -e, 2018-03-31)
clsCfg: Any = Config(testkey,Some(2018-01-01),Some(2018-03-31))
clsCfg: Config(testkey,Some(2018-01-01),Some(2018-03-31))
Arguments passed as array, one array element per row:
-k
testkey
-s
2018-01-01
-e
2018-03-31
<console>:42: error: value keyVal is not a member of Any
           println("clscfg.keyVal" + clsCfg.keyVal)

But when I remove the try/catch lines as follows:

  import org.sellmerfud.optparse._
  case class Config(keyVal: String            = "tst",
                  startDate: String          = "",
                  endDate: String            = "")
  var args = Array("-k","testkey","-s","2018-01-01","-e","2018-03-31")
  var clsCfg =
    new OptionParser[Config] {
      banner = "testargs [options] file...\n"
      separator("")
      separator("Options: ")
      reqd[String]("-k <string>", "--keyval <string>", "Enter Key Value")
          { (v, cfg) => cfg.copy(keyVal = v) }
      optl[String]("-s", "--startdate <date>", "Enter date in mm-dd-yyyy format.")
          { (v, cfg) => cfg.copy(startDate = v.toString()) }
      optl[String]("-e", "--enddate <date>", "Enter date in mm-dd-yyyy format.")
          { (v, cfg) => cfg.copy(endDate = v.toString) }
    }.parse(args, Config())

  println("clsCfg: " + clsCfg)
  println("\nArguments passed as array, one array element per row:")
  println(args.deep.mkString("\n"))
  println("clscfg.keyVal: " + clsCfg.keyVal)

I get these results which does parse the string BUT More importantly it creates the variable clsCfg where I can access the class members easily.

import org.sellmerfud.optparse._
defined class Config
args: Array[String] = Array(-k, testkey, -s, 2018-01-01, -e, 2018-03-31)
clsCfg: Config = Config(testkey,Some(2018-01-01),Some(2018-03-31))
clsCfg: Config(testkey,Some(2018-01-01),Some(2018-03-31))
Arguments passed as array, one array element per row:
-k
testkey
-s
2018-01-01
-e
2018-03-31
clscfg.keyVal: testkey   

I think that I need the try/catch to properly handle malformed variables handed in, but I don't know how to get that working. Any help is appreciated.


Solution

  • What's happening can be shown in an example. Basically, when you use try..catch, scala will look at each return type of all the code blocks (try and all the catch, since there may be multiple) and return the one type that is common to all of them. In your case, it's Any. Example:

    val a  =  try { 1 } // a will be of type Int
    val b =   try { 1 } catch { _ => 2 } // b will be of type Int
    val c =   try { 1 } catch { _ => "blah" } // c will be of type Any
    

    If you want a more scala-ish way to handle it, you could do as somebody is suggesting in the comments and use the Try() monad, but you could build a function that returns Option[Config]

    def buildConfig(args:Array[String]):Option[Config] = try { 
     Some(new OptionParser[Config] {
      ....
      }.parse(args, Config())
    } catch { _ => None }   
    

    Note that both try and catch return a Option, since Some() and None are both an Option. Then use pattern matching to make sure the processing was successful

    buildConfig(args) match {
         case Some(conf) => // normal processing
         case None => //
    }