Why cannot this case of implicit conversions be optimized?
Asked Answered
C

2

6

Why cannot Scala optimize the following:

a.

implicit def whatever[A](a: A) = new { ... }

to:

b.

class some$generated$name(a: A) {
  ...
}
implicit def whatever[A](a: A) = new some$generated$name(a)

?

Why does it have to use structural typing in this case? I would like Scala compiler to perform this optimization as writing in style b is just too ugly (because, 1. locality of logic is lost, 2. you have to unnecessarily invent names for these additional explicit classes), and a is far less performant than b.

Catricecatrina answered 6/10, 2010 at 2:25 Comment(3)
That isn't "structural typing." Those are implicit wrapper objects for extension methods. Structural typing is when you define types in terms of what methods or fields they have, such as type Closeable = Any{ def close: Unit } Consonantal
@MJP: I remember having read it somewhere that code as in a uses structural types.Catricecatrina
@MJP: Yes, and new { ... } is precisely defined in terms of what methods and fields it has...Petersburg
P
3

I think it could, and this can be done with a compiler plugin, to look something like

@extension implicit def whatever[A](a: A) = new { ... }

But I don't know if anyone has written such a plugin yet...

UPDATE:

If I compile this file:

object Main {
  implicit def option[A](a: A) = new { def id = a }  

  def foo(x: String) = x.id
}

and decompile code for foo, reflection is still involved:

F:\MyProgramming\raw>javap -c Main$
Compiled from "Main.scala"
public final class Main$ extends java.lang.Object implements scala.ScalaObject{
public static final Main$ MODULE$;

public static {};
  Code:
   0:   new     #9; //class Main$
   3:   invokespecial   #12; //Method "<init>":()V
   6:   return

public static java.lang.reflect.Method reflMethod$Method1(java.lang.Class);
  Code:
   0:   getstatic       #20; //Field reflPoly$Cache1:Ljava/lang/ref/SoftReference;
   3:   invokevirtual   #27; //Method java/lang/ref/SoftReference.get:()Ljava/lang/Object;
   6:   checkcast       #29; //class scala/runtime/MethodCache
   9:   ifnonnull       29
   12:  new     #23; //class java/lang/ref/SoftReference
   15:  dup
   16:  new     #31; //class scala/runtime/EmptyMethodCache
   19:  dup
   20:  invokespecial   #32; //Method scala/runtime/EmptyMethodCache."<init>":()V
   23:  invokespecial   #35; //Method java/lang/ref/SoftReference."<init>":(Ljava/lang/Object;)V
   26:  putstatic       #20; //Field reflPoly$Cache1:Ljava/lang/ref/SoftReference;
   29:  getstatic       #20; //Field reflPoly$Cache1:Ljava/lang/ref/SoftReference;
   32:  invokevirtual   #27; //Method java/lang/ref/SoftReference.get:()Ljava/lang/Object;
   35:  checkcast       #29; //class scala/runtime/MethodCache
   38:  aload_0
   39:  invokevirtual   #38; //Method scala/runtime/MethodCache.find:(Ljava/lang/Class;)Ljava/lang/r
eflect/Method;
   42:  astore_1
   43:  aload_1
   44:  ifnull  49
   47:  aload_1
   48:  areturn
   49:  aload_0
   50:  ldc     #40; //String id
   52:  getstatic       #42; //Field reflParams$Cache1:[Ljava/lang/Class;
   55:  invokevirtual   #48; //Method java/lang/Class.getMethod:(Ljava/lang/String;[Ljava/lang/Class
;)Ljava/lang/reflect/Method;
   58:  astore_1
   59:  new     #23; //class java/lang/ref/SoftReference
   62:  dup
   63:  getstatic       #20; //Field reflPoly$Cache1:Ljava/lang/ref/SoftReference;
   66:  invokevirtual   #27; //Method java/lang/ref/SoftReference.get:()Ljava/lang/Object;
   69:  checkcast       #29; //class scala/runtime/MethodCache
   72:  aload_0
   73:  aload_1
   74:  invokevirtual   #52; //Method scala/runtime/MethodCache.add:(Ljava/lang/Class;Ljava/lang/ref
lect/Method;)Lscala/runtime/MethodCache;
   77:  invokespecial   #35; //Method java/lang/ref/SoftReference."<init>":(Ljava/lang/Object;)V
   80:  putstatic       #20; //Field reflPoly$Cache1:Ljava/lang/ref/SoftReference;
   83:  aload_1
   84:  areturn

public java.lang.Object option(java.lang.Object);
  Code:
   0:   new     #59; //class Main$$anon$1
   3:   dup
   4:   aload_1
   5:   invokespecial   #60; //Method Main$$anon$1."<init>":(Ljava/lang/Object;)V
   8:   areturn

public java.lang.String foo(java.lang.String);
  Code:
   0:   aload_0
   1:   aload_1
   2:   invokevirtual   #69; //Method option:(Ljava/lang/Object;)Ljava/lang/Object;
   5:   astore_2
   6:   aconst_null
   7:   astore_3
   8:   aload_2
   9:   invokevirtual   #75; //Method java/lang/Object.getClass:()Ljava/lang/Class;
   12:  invokestatic    #77; //Method reflMethod$Method1:(Ljava/lang/Class;)Ljava/lang/reflect/Metho
d;
   15:  aload_2
   16:  iconst_0
   17:  anewarray       #71; //class java/lang/Object
   20:  invokevirtual   #83; //Method java/lang/reflect/Method.invoke:(Ljava/lang/Object;[Ljava/lang
/Object;)Ljava/lang/Object;
   23:  astore_3
   24:  aload_3
   25:  checkcast       #85; //class java/lang/String
   28:  checkcast       #85; //class java/lang/String
   31:  areturn
   32:  astore  4
   34:  aload   4
   36:  invokevirtual   #91; //Method java/lang/reflect/InvocationTargetException.getCause:()Ljava/l
ang/Throwable;
   39:  athrow
  Exception table:
   from   to  target type
     8    24    32   Class java/lang/reflect/InvocationTargetException


}

Compare with

object Main2 {
  class Whatever[A](a: A) { def id = a }

  implicit def option[A](a: A) = new Whatever(a)

  def foo(x: String) = x.id
}

And decompiling:

F:\MyProgramming\raw>javap -c Main2$
Compiled from "Main2.scala"
public final class Main2$ extends java.lang.Object implements scala.ScalaObject{
public static final Main2$ MODULE$;

public static {};
  Code:
   0:   new     #9; //class Main2$
   3:   invokespecial   #12; //Method "<init>":()V
   6:   return

public Main2$Whatever option(java.lang.Object);
  Code:
   0:   new     #16; //class Main2$Whatever
   3:   dup
   4:   aload_1
   5:   invokespecial   #20; //Method Main2$Whatever."<init>":(Ljava/lang/Object;)V
   8:   areturn

public java.lang.String foo(java.lang.String);
  Code:
   0:   aload_0
   1:   aload_1
   2:   invokevirtual   #30; //Method option:(Ljava/lang/Object;)LMain2$Whatever;
   5:   invokevirtual   #34; //Method Main2$Whatever.id:()Ljava/lang/Object;
   8:   checkcast       #36; //class java/lang/String
   11:  areturn

}

F:\MyProgramming\raw>javap -c Main2$Whatever
Compiled from "Main2.scala"
public class Main2$Whatever extends java.lang.Object implements scala.ScalaObject{
public java.lang.Object id();
  Code:
   0:   aload_0
   1:   getfield        #14; //Field a:Ljava/lang/Object;
   4:   areturn

public Main2$Whatever(java.lang.Object);
  Code:
   0:   aload_0
   1:   aload_1
   2:   putfield        #14; //Field a:Ljava/lang/Object;
   5:   aload_0
   6:   invokespecial   #22; //Method java/lang/Object."<init>":()V
   9:   return

}
Petersburg answered 6/10, 2010 at 6:18 Comment(2)
bad news! can you comment on why things are this way?Hallucinosis
That's how structural types normally work (see infoscience.epfl.ch/record/138931/files). This just means that this construction isn't special-cased.Petersburg
H
2

I've read that too, and have often wanted to ask this same question. But on 2.8, I'm trying it out:

object Main {
  implicit def whatever[A](a: A) = new { def foo = "bar" }  
}

And when I javap it:

public final class Main$$anon$1 extends java.lang.Object{
  public java.lang.String foo();
  public Main$$anon$1();
}

Looks like good news, no?

Update You can track this optimization using this trac item

Hallucinosis answered 16/10, 2010 at 12:22 Comment(1)
Try the same with "".foo. Or more directly, scala -Xprint:jvm.Psychodynamics

© 2022 - 2024 — McMap. All rights reserved.