Wednesday, March 11, 2009

Dynamic method calls in Scala

This is a trick that I showed to quite experienced Scala coders and some were surprised by it... Let's give it some persistence in the blogosphere.
If you spent some time hacking Scala you probably heard of "asInstanceOf[T]". Which is a Scala's notation for casting. You probably used it in the way similar to this


serviceRegister.get('serviceIdentifier).asInstanceOf[SomeServiceClass].method()

serviceRegister.get() finds a service implementor in the register and returns base, abstract thing... So to use the service you  need to cast it.
Second thing you probably know as a Scala hacker is structural typing - kind of explicit, type safe duck typing. For example method's parameter may have it's type specified as "an object that has a method that is named "add" and it has two Int parameters and results in an Int".


def callsAdd(obj: {def add(a: Int, b: Int): Int}) { 
    /* does something and calls obj.add() */ 
  }

What happens when you mix both asInstanceOf and structural types?


println( anObject.asInstanceOf[{def add(a: Int, b: Int): Int}].add(1, 2) )

Voila! An easiest way to reflectively call a Java/Scala method in a compiled language.
Compare this to Java's way of doing reflective call:


import java.lang.reflect.*;
    
public class method2 {
   public int add(int a, int b)
   {
      return a + b;
   }
    
   public static void main(String args[])
   {
      try {
        Class cls = Class.forName("method2");
        Class partypes[] = new Class[2];
         partypes[0] = Integer.TYPE;
         partypes[1] = Integer.TYPE;
         Method meth = cls.getMethod(
           "add", partypes);
         method2 methobj = new method2();
         Object arglist[] = new Object[2];
         arglist[0] = new Integer(37);
         arglist[1] = new Integer(47);
         Object retobj 
           = meth.invoke(methobj, arglist);
         Integer retval = (Integer)retobj;
         System.out.println(retval.intValue());
      }
      catch (Throwable e) {
         System.err.println(e);
      }
   }
}

3 comments:

  1. And if you need use that structural type frequently, or othewise want to make the code more readable, you can use type alias.

    type add = {def add(a: Int, b: Int): Int}
    anObject.asInstanceOf[add].add(1, 2)

    ReplyDelete
  2. awesome trick, very simple yet very clever

    ReplyDelete
  3. The comparison is great :D it looks like you'd compare a high-level language with some assembler code :D.

    Very nice indeed!

    ReplyDelete