3

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(...) = { ... }
    }
    implicit 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)?

Raphael
  • 9,779
  • 5
  • 63
  • 94

2 Answers2

7

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.

Daniel C. Sobral
  • 295,120
  • 86
  • 501
  • 681
  • Thanks for these insights. I am curious, though, how this explanation fits together with paolo's findings which indicate that there _is_ a type in the JVM that could be used without reflection. Or is `test$$anon$1` only a class name but not a type? – Raphael Mar 07 '11 at 20:34
  • 1
    @Raphael It's like the difference between an interface and an implementation. The class `test$$anon$1` can be thought of as an implementation of an interface, while `AnyRef { def foo(...): ... }` would be the interface itself. While it is true that in limited scenarios one _could_ replace the reflection calls with these anonymous classes, that doesn't hold true for more general scenarios. – Daniel C. Sobral Mar 07 '11 at 20:57
  • thanks for the explanation. I was assuming `new {foo = "bar"}` was equivalent to `new Compiler_Please_Create_This_Class_For_Me()` as for anonymous classes in c# and java and not to `new AnyRef {foo = "bar"}`, so I was finding it hard to connect anonymous classes to structural types. – Paolo Falabella Mar 08 '11 at 11:45
6

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.

Paolo Falabella
  • 24,914
  • 3
  • 72
  • 86
  • Can you offer some evidence/proof for your belief? I was thinking of adding to types I do not control, otherwise traits are the better solution of course. I clarifies the question in that regard. – Raphael Mar 07 '11 at 15:29
  • @Raphael I don't have scala installed here and can't give you evidence. In a few hours when I'm home I'll compile both variants, check the generated .class and post the results – Paolo Falabella Mar 07 '11 at 16:22
  • 1
    @Raphael turns out you were right not to trust untested claims :) I revised my answer. – Paolo Falabella Mar 07 '11 at 18:07
  • Any case where a structural type is used (like the second example) will compile to use reflection and will hence impose a performance penalty. The first way, of adding a class to specifically provide the extension is the canonical way to do this (and is used extensively in the Scala standard libraries.) – Kris Nuttycombe Mar 07 '11 at 18:31