开发者

How to implement intermediate types for implicit methods?

开发者 https://www.devze.com 2023-02-15 02:23 出处:网络
Assume I want to offer method foo on existing type A outside of my control. As far as I know, the canonical way to do this in Scala is implementing an implicit conversion from A to some type that impl

Assume I want to offer method foo on existing type A outside of my control. As far as I know, the canonical way to do this in Scala is implementing an implicit conversion from A to some type that implements foo. Now I basically see two options.

  1. Define a separate, maybe even hidden class for the purpose:

    protected class Fooable(a : A) {
      def foo(...) = { ... }
    }
    implici开发者_如何学Pythont def a2fooable(a : A) = new Fooable(a)
    
  2. Define an anonymous class inline:

    implicit def a2fooable(a : A) = new { def foo(...) = { ... } }
    

Variant 2) is certainly less boilerplate, especially when lots of type parameters happen. On the other hand, I think it should create more overhead since (conceptually) one class per conversion is created, as opposed to one class globally in 1).

Is there a general guideline? Is there no difference, because compiler/VM get rid of the overhead of 2)?


Using a separate class is better for performance, as the alternative uses reflection.

Consider that

new { def foo(...) = { ... } }

is really

new AnyRef { def foo(...) = { ... } }

Now, AnyRef doesn't have a method foo. In Scala, this type is actually AnyRef { def foo(...): ... }, which, if you remove AnyRef, you should recognize as a structural type.

At compile time, this time can be passed back and forth, and everywhere it will be known that the method foo is callable. However, there's no structural type in the JVM, and to add an interface would require a proxy object, which would cause some problems such as breaking referential equality (ie, an object would not be equal with a structural type version of itself).

The way found around that was to use cached reflection calls for structural types.

So, if you want to use the Pimp My Library pattern for any performance-sensitive application, declare a class.


I believe 1 and 2 get compiled to the same bytecode (except for the class name that gets generated in case 2). If Fooable exists only for you to be able to convert implicitly A to Fooable (and you're never going to directly create and use a Fooable), then I would go with option 2.

However, if you control A (meaning A is not a java library class that you can't subclass) I would consider using a trait instead of implicit conversions to add behaviour to A.

UPDATE: I have to reconsider my answer. I would use variant 1 of your code, because variant 2 turns out to be using reflection (scala 2.8.1 on Linux).

I compiled these two versions of the same code, decompiled them to java with jd-gui and here are the results:

source code with named class

class NamedClass { def Foo : String = "foo" }

object test {
  implicit def StrToFooable(a: String) = new NamedClass 
  def main(args: Array[String]) { println("bar".Foo) }
}

source code with anonymous class

object test {
  implicit def StrToFooable(a: String) = new { def Foo : String = "foo" } 

  def main(args: Array[String]) { println("bar".Foo) }
}    

compiled and decompiled to java with java-gui. The "named" version generates a NamedClass.class that gets decompiled to this java:

public class NamedClass
  implements ScalaObject
{
  public String Foo()
  {
    return "foo";
  }
}

the anonymous generates a test$$anon$1 class that gets decompiled to the following java

public final class test$$anon$1
{
  public String Foo()
  {
    return "foo";
  }
}

so almost identical, except for the anonymous being "final" (they apparently want to make extra sure you won't get out of your way to try and subclass an anonymous class...)

however at the call site I get this java for the "named" version

public void main(String[] args) 
{ 
  Predef..MODULE$.println(StrToFooable("bar").Foo());
}

and this for the anonymous

  public void main(String[] args) { 
    Object qual1 = StrToFooable("bar"); Object exceptionResult1 = null;
    try { 
      exceptionResult1 = reflMethod$Method1(qual1.getClass()).invoke(qual1, new Object[0]); 
      Predef..MODULE$.println((String)exceptionResult1); 
      return; 
    } catch (InvocationTargetException localInvocationTargetException) { 
      throw localInvocationTargetException.getCause();
    }
  }

I googled a little and found that others have reported the same thing but I haven't found any more insight as to why this is the case.

0

精彩评论

暂无评论...
验证码 换一张
取 消