views:

149

answers:

4

I write a number of simple scala scripts that end up starting with a simple pattern match on args like:

val Array(path, foo, whatever) = args
// .. rest of the script uses "path", "foo", etc.

Of course, if I supply the wrong number of arguments, I get an inscrutable error like:

scala.MatchError: [Ljava.lang.String;@7786df0f
    at Main$$anon$1.<init>(FollowUsers.scala:5)
    ...

Is there an easy way to give a more useful error message? My current workaround is to do something like:

args match {
  case Array(path, foo, whatever) => someFunction(path, foo, whatever)
  case _ => System.err.println("usage: path foo whatever")
}
def someFunction(path: String, foo: String, whatever: String) = {
  // .. rest of the script uses "path", "foo", etc.
}

But that feels like a lot of boilerplate what with having to define a whole other function, and having to repeat "path", "foo" and "whatever" in so many places. Is there a better way? I guess I could lose the function and put the body in the match statement, but that seems less readable to me.

I know I could use one of the many command line argument parsing packages, but I'm really looking for something extremely lightweight that I don't have to add a dependency and modify my classpath for.

+1  A: 

One way is to catch MatchError:

try {
  val Array(path, foo, whatever) = args
} catch {
  case _: MatchError => System.err.println("usage: path foo whatever")
}
michid
But then "path", "foo" and "whatever" will be out of scope after the try/catch. So you'll have to do basically the same thing as the code above - either add an auxiliary function, or define the whole main method in the try block.
Steve
+3  A: 
scala> val args = Array("evil", "mad", "scientist")
args: Array[java.lang.String] = Array(evil, mad, scientist)

scala> def logToConsole(th: Throwable) { Console.err.println("Usage: path foo bar") }
logToConsole: (th: Throwable)Unit

scala> handling(classOf[MatchError]) by logToConsole apply {
     |   val Array(path, foo, bar) = args
     |   println(path)
     | }
evil

scala> handling(classOf[MatchError]) by logToConsole apply {
     |   val Array(path, foo, bar) = Array("#fail")
     |   println(path)
     | }
Usage: path foo bar
missingfaktor
Yep, I just came up with about the same thing. The downside of this approach is that if anything else in the script throws a MatchError, the handler will make it look like the command line arguments were wrong, hiding the actual match error.
Steve
A: 

Struck me that maybe the new util.control.Exception might have a solution:

import scala.util.control.Exception

Exception.handling(classOf[scala.MatchError]).by{
  e => System.err.println("usage: path foo whatever")
} {
  val Array(path, foo, whatever) = args
  // .. rest of the script uses "path", "foo", etc.
}

This at least puts the error handling first and keeps the rest of the code together, though it makes me a little nervous to have such a large try block (that second block with the Array pattern matching is essentially all in the same try block being handled by Exception.handling).

EDIT: Looks like Missing Faktor posted about the same thing too, but with an explicitly defined function and an explicit call to apply.

Steve
+1  A: 

How about?

val Array(path, foo, whatever) = if (args.length == 3) args 
  else throw new Exception("usage:path foo whatever")

==edit==

based on Randall's comment:

require(args.length == 3, "usage: path foo whatever")
val Array(path, foo, whatever) = args

That's minimum boilerplate. Your vals are in scope, you don't have to deal with closing brace and you get the usage error message.

huynhjl
Yeah, I guess that's okay, though you'll get a stack trace instead of a straightforward error message.
Steve
This is what `require` is meant for. E.g. `require(args.length == 3, "args must have length three in call to XYZ")`
Randall Schulz
The version with "require" is nice and simple. You do get a pretty verbose exception with that though: `java.lang.IllegalArgumentException: requirement failed: usage: path foo whatever` (and then all the usual traceback). Makes perfect sense to a Scala developer, but might be a little confusing for someone who doesn't know Scala but just wants to use my script.
Steve
@Steve yes, I assumed you were the only intended user. If you want to distribute the script, then one of the other answer may be better.
huynhjl
@Steve: Why not define a variation of `require` as per your requirements? http://paste.pocoo.org/show/260337/
missingfaktor
@missing-faktor yeah, that would work. It would mean re-defining that function in each script though. But it is a pretty simple function. =)
Steve