2010-04-19 12 views
18

Actualmente estoy buscando implementaciones de cierre en diferentes idiomas. En lo que respecta a Scala, sin embargo, no puedo encontrar ninguna documentación sobre cómo se correlaciona un cierre con objetos Java.¿Cómo se transforman los cierres Scala en objetos Java?

Está bien documentado que las funciones de Scala se asignan a objetos FunctionN. Supongo que la referencia a la variable libre del cierre debe almacenarse en algún lugar de ese objeto de función (como se hace en C++ 0x, por ejemplo).

También probé la compilación de lo siguiente con scalac descompilación y luego los archivos de clase con JD:

object ClosureExample extends Application { 
    def addN(n: Int) = (a: Int) => a + n 
    var add5 = addN(5) 
    println(add5(20)) 
} 

En las fuentes descompilados, veo un subtipo anónima de Función1, que debería ser mi cierre. Pero el método apply() está vacío y la clase anónima no tiene campos (que podrían almacenar las variables de cierre). Supongo que el decompilador no logró obtener la parte interesante de los archivos de clase ...

ahora a las preguntas:

  • ¿Conoce cómo la transformación se hace exactamente?
  • ¿Sabes dónde está documentado?
  • ¿Tienes otra idea de cómo podría resolver el misterio?
+1

¿Estás seguro de que estás utilizando 'javap -c 'ClassName $$ anonfun $ etcetc'' para hacer la descompilación? –

Respuesta

31

Vamos a desmontar un conjunto de ejemplos para que podamos ver cómo difieren. (Si se utiliza RC1, compilar con -no-specialization para mantener las cosas más fáciles de entender.)

class Close { 
    var n = 5 
    def method(i: Int) = i+n 
    def function = (i: Int) => i+5 
    def closure = (i: Int) => i+n 
    def mixed(m: Int) = (i: Int) => i+m 
} 

En primer lugar, vamos a ver qué hace method:

public int method(int); 
    Code: 
    0: iload_1 
    1: aload_0 
    2: invokevirtual #17; //Method n:()I 
    5: iadd 
    6: ireturn 

bastante sencillo. Es un método. Cargue el parámetro, invoque el captador para n, agregar, devolver. Se parece a Java.

¿Qué tal function?En realidad, no cierra ningún dato, pero es una función anónima (llamada Close$$anonfun$function$1). Si hacemos caso de cualquier especialidad, el constructor y se aplican son de mayor interés:

public scala.Function1 function(); 
    Code: 
    0: new #34; //class Close$$anonfun$function$1 
    3: dup 
    4: aload_0 
    5: invokespecial #35; //Method Close$$anonfun$function$1."<init>":(LClose;)V 
    8: areturn 

public Close$$anonfun$function$1(Close); 
    Code: 
    0: aload_0 
    1: invokespecial #43; //Method scala/runtime/AbstractFunction1."<init>":()V 
    4: return 

public final java.lang.Object apply(java.lang.Object); 
    Code: 
    0: aload_0 
    1: aload_1 
    2: invokestatic #26; //Method scala/runtime/BoxesRunTime.unboxToInt:(Ljava/lang/Object;)I 
    5: invokevirtual #28; //Method apply:(I)I 
    8: invokestatic #32; //Method scala/runtime/BoxesRunTime.boxToInteger:(I)Ljava/lang/Integer; 
    11: areturn 

public final int apply(int); 
    Code: 
    0: iload_1 
    1: iconst_5 
    2: iadd 
    3: ireturn 

Así, se carga un puntero "this" y crea un nuevo objeto que toma la clase envolvente como su argumento. Esto es estándar para cualquier clase interna, realmente. La función no necesita hacer nada con la clase externa por lo que solo llama al constructor del super. Luego, cuando llame solicite, realice los trucos box/unbox y luego invoque los cálculos reales; es decir, simplemente agregue 5.

Pero, ¿qué sucede si utilizamos un cierre de la variable dentro de Close? La configuración es exactamente lo mismo, pero ahora el constructor Close$$anonfun$closure$1 se ve así:

public Close$$anonfun$closure$1(Close); 
    Code: 
    0: aload_1 
    1: ifnonnull 12 
    4: new #48; //class java/lang/NullPointerException 
    7: dup 
    8: invokespecial #50; //Method java/lang/NullPointerException."<init>":()V 
    11: athrow 
    12: aload_0 
    13: aload_1 
    14: putfield #18; //Field $outer:LClose; 
    17: aload_0 
    18: invokespecial #53; //Method scala/runtime/AbstractFunction1."<init>":()V 
    21: return 

Es decir, se comprueba para asegurarse de que la entrada no es nulo (es decir, la clase externa no es nulo) y lo guarda en un campo. Ahora, cuando llega el momento de aplicarlo, después de que el boxeo/unboxing envoltorio:

public final int apply(int); 
    Code: 
    0: iload_1 
    1: aload_0 
    2: getfield #18; //Field $outer:LClose; 
    5: invokevirtual #24; //Method Close.n:()I 
    8: iadd 
    9: ireturn 

ves que utiliza este campo para referirse a la clase padre, e invoca el captador de n. Agregar, regresar, hecho. Por lo tanto, los cierres son bastante fáciles: el constructor de función anónima solo guarda la clase adjunta en un campo privado.

Ahora, ¿qué pasa si cerramos no una variable interna, pero un argumento de método? Eso es lo que hace Close$$anonfun$mixed$1. En primer lugar, un vistazo a lo que hace el método mixed:

public scala.Function1 mixed(int); 
    Code: 
    0: new #39; //class Close$$anonfun$mixed$1 
    3: dup 
    4: aload_0 
    5: iload_1 
    6: invokespecial #42; //Method Close$$anonfun$mixed$1."<init>":(LClose;I)V 
    9: areturn 

Se carga el parámetro m antes de llamar al constructor! Por lo tanto, no sorprende que el constructor tenga el siguiente aspecto:

public Close$$anonfun$mixed$1(Close, int); 
    Code: 
    0: aload_0 
    1: iload_2 
    2: putfield #18; //Field m$1:I 
    5: aload_0 
    6: invokespecial #43; //Method scala/runtime/AbstractFunction1."<init>":()V 
    9: return 

donde ese parámetro se guarda en un campo privado. No se guarda ninguna referencia a la clase externa porque no la necesitamos. Y usted no debe sorprenderse al aplicar:

public final int apply(int); 
    Code: 
    0: iload_1 
    1: aload_0 
    2: getfield #18; //Field m$1:I 
    5: iadd 
    6: ireturn 

Sí, solo cargamos ese campo almacenado y hacemos nuestras cuentas.

No estoy seguro de lo que estaba haciendo para no ver esto con su ejemplo: los objetos son un poco difíciles porque tienen las clases MyObject y MyObject$ y los métodos se dividen entre los dos de una manera que puede no ser intuitivo. Pero la aplicación definitivamente aplica cosas, y en general todo el sistema funciona de la manera que uno esperaría (después de que te sientas y lo pienses realmente duro durante mucho tiempo).

+0

¡Hermosa respuesta! –

+0

Muchas gracias, eso es muy detallado. Estaba tratando de usar un descompilador que reconstruye el código Java (se llama JD), porque no estoy acostumbrado a bytecode, y eso no funcionó.Creo que el decompilador no maneja las clases anónimas correctamente. Ahora que aprendí los conceptos básicos sobre bytecode de Java, su respuesta es fácil de entender. ¿Conoces alguna documentación oficial sobre esta transformación? – theDmi

+0

@iguana: No sé en ninguna parte que la transformación esté documentada en detalle. Tiendo a creer bytecode más que documentos de todos modos. –

5

diferencia de las clases internas anónimas de Java que son pseudo-cierres y no pueden modificar las variables que aparecen que ser cerrado en su entorno, cierres de Scala son reales, por lo que el código de cierre hace referencia directamente los valores en el entorno circundante . Dichos valores se compilan de forma diferente cuando se hace referencia a ellos desde un cierre para que esto sea posible (dado que no hay forma de que el código de método acceda a los locales desde ningún marco de activación distinto al actual).

Por el contrario, en Java sus valores se copian a campos en la clase interna, por lo que el lenguaje requiere que los valores originales en el entorno adjunto sean final, por lo que nunca pueden divergir.

Debido a que toda la función Scala de literales/referencias de cierre a valores en el entorno envolvente están en el código de la función del literal método apply(), que no aparecen como campos en la Function subclase real generada por la función literal.

No sé cómo está descompilando, pero los detalles de cómo lo hizo probablemente expliquen por qué no está viendo ningún código para el cuerpo del método apply().

+0

Gracias, eso es lo que sospechaba. Investigaré más sobre mi problema de descompilación. ¿Tiene alguna fuente oficial, donde este comportamiento está documentado? Eso sería muy útil, porque necesito algo que pueda citar :-). – theDmi

+0

Nada de lo que escribí es autoritario. Supongo que debería haber dicho eso ... Existe el Scala Compiler Corner (http://www.sts.tu-harburg.de/people/mi.garcia/ScalaCompilerCorner/) que es un buen recurso y puede contener dicha información. –

Cuestiones relacionadas