Why scala uses reflection to call method on structural type?
Asked Answered
F

2

17

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();}
    });
  }
}
Fatalism answered 16/12, 2011 at 19:55 Comment(0)
T
15

Consider a simple proposition:

type T = { def quack(): Unit; def walk(): Unit }
def f(a: T, b: T) = 
  if (a eq b) println("They are the same duck!")
  else        println("Different ducks")

f(x, x) // x is a duck

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...

Telephoto answered 16/12, 2011 at 20:11 Comment(5)
I don't get the last sentence, can you, please, explain?Donnelly
@Donnelly invokevirtual is not present on JVM 1.5 and 1.6, so Scala can't rely on it. Scala 2.10 will actually deprecate JVM 1.5, but it's still some time before Scala can take advantage of things present only on JVM 1.7.Telephoto
@Daniel C. Sobral: I guess you meant invokedynamic instead of invokevirtual in your last commentFatalism
@OpDeCirkel You are correct. The use of invokedynamic would be an alternative to what's being done that would not suffer the identity loss of the question's proposed solution, but would depend on not supporting Java 1.5/1.6. That's not far fetched any longer -- there was a community poll on whether to require Java 7 for Scala 2.11. I don't know what was the result, but it is something that is being considered nowadays.Telephoto
The poll resulted in a decision to keep 1.6 around for the time being, and add experimental support for 1.7 code generation, with a possible eye to jump over 1.7 straight to 1.8 in whatever the next version of Scala will be after 2.11. grokbase.com/t/gg/scala-internals/1337pmhy48/…Greenberg
A
10

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.

Avrom answered 16/12, 2011 at 20:12 Comment(1)
@Daniel C. Sobral and Dave Griffith : Both of your answers are acceptable. So I had to toss a coin to formally accept one.Fatalism

© 2022 - 2024 — McMap. All rights reserved.