recorrer - scala java download
¿Cómo se transforman los cierres de Scala en objetos Java? (2)
A diferencia de las clases internas anónimas de Java que son pseudo-cierres y no pueden modificar las variables que parecen estar cerradas en su entorno, los cierres de Scala son reales, por lo que el código de cierre hace referencia directamente a los valores en el entorno circundante. Dichos valores se compilan de manera diferente cuando se hace referencia a ellos desde un cierre para hacer esto posible (ya que no hay manera de que el código del método acceda a los locales desde cualquier marco de activación que no sea el actual).
Por el contrario, en Java, sus valores se copian en los campos de la clase interna, por lo que el lenguaje requiere que los valores originales en el entorno de cierre sean final
, para que nunca puedan divergir.
Debido a que todas las referencias de literales / cierres de la función de Scala a los valores en el entorno adjunto están en el código del método apply()
de los literales de funciones, no aparecen como campos en la subclase de Function
real generada para el literal de funciones.
No sé cómo se está descompilando, pero los detalles de cómo lo hizo probablemente explican por qué no está viendo ningún código para el cuerpo del método apply()
.
Actualmente estoy buscando implementaciones de cierre en diferentes idiomas. Sin embargo, cuando se trata de Scala, no puedo encontrar ninguna documentación sobre cómo se asigna un cierre a los objetos de 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 intenté compilar lo siguiente con scalac y luego descompilar 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 descompiladas, veo un subtipo anónimo de Function1, que debería ser mi cierre. Pero el método apply () está vacío, y la clase anónima no tiene campos (lo que potencialmente podría almacenar las variables de cierre). Supongo que el descompilador no logró sacar la parte interesante de los archivos de clase ...
Ahora a las preguntas:
- ¿Sabes cómo se hace exactamente la transformación?
- ¿Sabes dónde está documentado?
- ¿Tienes otra idea de cómo podría resolver el misterio?
Vamos a separar un conjunto de ejemplos para que podamos ver cómo difieren. (Si usa RC1, compile con -no-specialization
para que las cosas sean 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
}
Primero, veamos que method
hace:
public int method(int);
Code:
0: iload_1
1: aload_0
2: invokevirtual #17; //Method n:()I
5: iadd
6: ireturn
Muy claro. Es un metodo Cargue el parámetro, invoque el captador para n
, agregue, devuelva. Se parece a Java.
¿Qué tal la function
? En realidad no cierra ningún dato, pero es una función anónima (llamada Close$$anonfun$function$1
). Si ignoramos cualquier especialización, el constructor y la aplicación 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
Por lo tanto, carga un puntero de "esto" y crea un nuevo objeto que toma la clase adjunta como su argumento. Esto es estándar para cualquier clase interna, realmente. La función no necesita hacer nada con la clase externa, así que solo llama al constructor del super. Luego, cuando se aplica la llamada, haces los trucos de box / unbox y luego llamas a la matemática real, es decir, solo agregas 5.
¿Pero qué pasa si utilizamos un cierre de la variable dentro de Cerrar? La configuración es exactamente la misma, pero ahora el constructor Close$$anonfun$closure$1
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, verifica que la entrada no sea nula (es decir, que la clase externa no sea nula) y la guarda en un campo. Ahora, cuando llegue el momento de aplicarlo, después del envoltorio de boxeo / desempaquetado:
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
ve que usa ese campo para referirse a la clase padre e invoca al captador para n
. Añadir, volver, listo. Por lo tanto, los cierres son bastante fáciles: el constructor de funciones anónimas simplemente guarda la clase adjunta en un campo privado.
Ahora, ¿qué pasa si cerramos no una variable interna, sino un argumento de método? Eso es lo que hace Close$$anonfun$mixed$1
. Primero, mira 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
¡Carga el parámetro m
antes de llamar al constructor! Así que no es de extrañar que el constructor se vea así:
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 tampoco debes sorprenderte 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 nuestros cálculos.
No estoy seguro de lo que estaba haciendo para no ver esto con su ejemplo: los objetos son un poco complicados porque tienen clases MyObject
y MyObject$
y los métodos se dividen entre los dos de una manera que puede no ser intuitiva. Pero aplicar definitivamente aplica cosas, y en general, todo el sistema funciona de la manera que esperabas (después de que te sientas y lo pienses muy duro durante mucho tiempo).