If function accepts structural type, it can be defined as:
def doTheThings(duck: { def walk; def quack }) { duck.quack }
or
type DuckType = { def walk; def quack }
def doTheThings(duck: DuckType) { duck.quack }
Then, you can use that function in following way:
class Dog {
def walk { println("Dog walk") }
def quack { println("Dog quacks") }
}
def main(args: Array[String]) {
doTheThings(new Dog);
}
If you decompile (to Java) the classes generated by scalac for my example, you can see that argument of doTheThings
is of type Object
and the implementation uses reflection to call methods on the argument (i.e.duck.quack
)
My question is why reflection? Isn't it possible just to use anonymous and invokevirtual instead of reflection?
Here is way to translate(implement) the structural type calls for my example (Java syntax, but the point is the bytecode):
class DuckyDogTest {
interface DuckType {
void walk();
void quack();
}
static void doTheThing(DuckType d) {
d.quack();
}
static class Dog {
public void walk() { System.out.println("Dog walk"); }
public void quack() { System.out.println("Dog quack"); }
}
public static void main(String[] args) {
final Dog d = new Dog();
doTheThing(new DuckType() {
public final void walk() { d.walk(); }
public final void quack() { d.quack();}
});
}
}
In addition to your proxy object implementing methods on the structural type, it would also need to have appropriate pass-through implementations of all of the methods on Any (equals, hashCode, toString, isInstanceOf, asInstanceOf) and AnyRef(getClass, wait, notify, notifyAll, and synchronized). While some of these would be straightforward, some would be almost impossible to get right. In particular, all of the methods listed are "final" on AnyRef (for Java compatability and security) and so couldn't be properly implemented by your proxy object.
Consider a simple proposition:
It would print
Different ducks
under your proposal. You could further refine it, but you just cannot keep referential equality intact using a proxy.A possible solution would be to use the type class pattern, but that would require passing another parameter (even if implicit). Still, it's faster. But that's mostly because of the lameness of Java's reflection speed. Hopefully, method handles will get around the speed problem. Unfortunately, Scala is not scheduled to give up on Java 5, 6 and 7 (which do not have method handles) for some time...