scalavariadic-functionsruntime-type

Check argument type (from a variable length argument list) at runtime in Scala


I am designing the interface for a system that can accept rules to be applied to a given data structures.

The main system is supposed to work as a driver receiving commands like "apply rule X to arguments U,V,W,...". I don't know all possible rules at compile time, so I thought to embed the argument type information on the rule definition and verify it latter.

Right now the rule definition is as follows:

trait Rule {
  val argTypes: Seq[Class[_]]
  def apply(stt: State, args: Seq[Any])
}

The number of actual arguments in args must match the number of type defined in argTypes, and the method apply should return an manipulate the state. (Actually, this is simplified explanation but this is the general idea).

I also implemented a function called checkTypes to verify if the types of the actual arguments match the type defined in argTypes.

def checkTypes(args: Seq[Any]) {
  if (argTypes.size != args.size) {
    val msg = "Number of arguments (%d) does not match expected number (%d)."
    throw new IllegalArgumentException(msg.format(args.size, argTypes.size))
  }
  val err = "Incompatible argument type for [%s]. Expected: %s. Found: %s."
  for (i <- 0 until argTypes.size) {
    val formalClass = argTypes(i)
    val arg = args(i)
    val actualClass = arg.asInstanceOf[AnyRef].getClass
    if (!(formalClass isAssignableFrom actualClass)) {
      val errMsg = err.format(arg, formalClass.getName, actualClass.getName)
      throw new IllegalArgumentException(errMsg)
    }
  }
}

The problem is that whenever I try to pass integer arguments (read from console or text file) the checkTypes procedure fails with this message: java.lang.IllegalArgumentException: Incompatible argument type for [1]. Expected: int. Found: java.lang.Integer.

I am converting the integer arguments with Integer.parseInt(t).asInstanceOf[Int] and the rule expects two arguments of type Int

So, are there a more effective way to check for type of arguments at runtime?

OR

How do I convert String to Int for real?

Thanks in advance.


As a minimum working example, this is a session in the Scala REPL that rises the exception:

scala> val argTypes: Seq[Class[_]] = Seq(classOf[Int], classOf[Int])
argTypes: Seq[Class[_]] = List(int, int)

scala> def checkTypes(args: Seq[Any]) {
     |   if (argTypes.size != args.size) {
     |     val msg = "Number of arguments (%d) does not match expected number (%d)."
     |     throw new IllegalArgumentException(msg.format(args.size, argTypes.size))
     |   }
     |   val err = "Incompatible argument type for [%s]. Expected: %s. Found: %s."
     |   for (i <- 0 until argTypes.size) {
     |     val formalClass = argTypes(i)
     |     val arg = args(i)
     |     val actualClass = arg.asInstanceOf[AnyRef].getClass
     |     if (!(formalClass isAssignableFrom actualClass)) {
     |       val errMsg = err.format(arg, formalClass.getName, actualClass.getName)
     |       throw new IllegalArgumentException(errMsg)
     |     }
     |   }
     | }
checkTypes: (args: Seq[Any])Unit

scala> val args: Seq[Any] = Seq("1".toInt, "2".toInt)
args: Seq[Any] = List(1, 2)

scala> checkTypes(args)
java.lang.IllegalArgumentException: Incompatible argument type for [1]. Expected: int. Found: java.lang.Integer.
    at $anonfun$checkTypes$1.apply$mcVI$sp(<console>:20)
    at scala.collection.immutable.Range.foreach$mVc$sp(Range.scala:78)
    at .checkTypes(<console>:14)
    at .<init>(<console>:11)
    at .<clinit>(<console>)
    at .<init>(<console>:11)
    at .<clinit>(<console>)
    at $print(<console>)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:616)
    at scala.tools.nsc.interpreter.IMain$ReadEvalPrint.call(IMain.scala:704)
    at scala.tools.nsc.interpreter.IMain$Request$$anonfun$14.apply(IMain.scala:920)
    at scala.tools.nsc.interpreter.Line$$anonfun$1.apply$mcV$sp(Line.scala:43)
    at scala.tools.nsc.io.package$$anon$2.run(package.scala:25)
    at java.lang.Thread.run(Thread.java:679)

Solution

  • You have a mismatch between the boxed type (java.lang.Integer) and the unboxed type (scala.Int == java int). The actual instance is boxed, but you're testing it against the primitive classOf.

    Here's an example of what happens when primitives are boxed:

    scala> 5.getClass
    res0: Class[Int] = int
    
    scala> (5: Any)
    res1: Any = 5
    
    scala> res1.getClass
    res2: Class[_] = class java.lang.Integer
    

    Note that if you pattern match on Any and get a primitive, it will actually pick out the boxed copy and unbox it for you.

    scala> res1 match { case i: Int => println(i); case _ => }
    5
    

    Since you know you must be boxed, given the code you wrote, you may as well just check for Integer instead of Int (that is, use classOf[Integer]).