How to implement intermediate types for implicit methods?

Suppose I want to suggest a foo method for an existing type A outside my control. As far as I know, the canonical way to do this in Scala is to implement an implicit conversion from A to some type that implements foo . Now I basically see two options.

  • Define a separate, possibly even hidden class for this purpose:

     protected class Fooable(a : A) { def foo(...) = { ... } } implicit def a2fooable(a : A) = new Fooable(a) 
  • Define an anonymous inline class:

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

Option 2), of course, less conditional, especially when there are many type parameters. On the other hand, I think this should create additional overhead, since (conceptually) one class is created for conversion, and not one class globally in 1).

Is there a general recommendation? Does it make any difference because the compiler / VM gets rid of overhead 2)?

+3
source share
2 answers

I believe that 1 and 2 are compiled into the same bytecode (except for the class name that is generated in case 2). If Fooable exists only so that you can implicitly convert A to Fooable (and you will never directly create and use Fooable), then I would go with option 2.

However, if you control A (which means that A is not a java library class that you cannot subclass), I would consider using the attribute instead of implicit conversions to add behavior to A.

UPDATE : I have to revise my answer. I would use option 1 of your code, because option 2, it turns out, uses reflection (scala 2.8.1 on Linux).

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

source code with a 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 in java with java-gui. The "named" version generates the NamedClass.class class, which is decompiled into this java:

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

anonymous generates test class $$ anon $ 1, which decompiles into the following java

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

so nearly identical, except for the anonymous one, which is "final" (they apparently want to make sure you don't go out of their way to try and subclasses of the anonymous class ...)

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

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

and this is for 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 bit and found that others reported the same thing, but I did not find a deeper understanding of why this is the case.

+6
source

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

Consider that

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

really

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

Now AnyRef has no foo method. In Scala, this type is actually AnyRef { def foo(...): ... } , which, if you delete AnyRef , you must recognize it as a structural type.

During compilation, this time can be passed back and forth, and it will be known everywhere that the foo method is callable. However, the JVM does not have a structural type, and a proxy object is required to add an interface, which can cause some problems, such as violation of relational equality (i.e., the Object will not be equal to the structure of the structural type itself).

The found method is to use cached reflection calls for structural types.

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

+7
source

Source: https://habr.com/ru/post/909127/


All Articles