Cierres en Scala vs Closures en Java
language-design java-8 (2)
1) ¿Se pueden usar identificadores de método para tipos de funciones?
Scala se dirige a JDK 5 y 6 que no tienen identificadores de método, por lo que aún no ha tratado de resolver ese problema.
2) ¿Podemos deshacernos de la declaración explícita de los parámetros del tipo "throws"?
Scala no tiene excepciones marcadas.
3) No permitir @Shared en las variables de índice de bucle antiguas.
Scala no tiene variables de índice de bucle. Aún así, la misma idea se puede expresar con cierto tipo de ciclo while. La semántica de Scala es bastante estándar aquí. Los enlaces de símbolos se capturan y si el símbolo pasa a mapear a una celda de referencia mutable, entonces en tu propia cabeza ya sea.
4) Manejar interfaces como Comparator que definen más de un método, todos menos uno provienen de Object
Los usuarios de Scala tienden a usar funciones (o funciones implícitas) para forzar funciones del tipo correcto a una interfaz. p.ej
[implicit] def toComparator[A](f : (A, A) => Int) = new Comparator[A] {
def compare(x : A, y : A) = f(x, y)
}
5) Especifique mapeo desde tipos de funciones a interfaces:
La biblioteca estándar de Scala incluye rasgos de FuncitonN para 0 <= N <= 22 y la especificación dice que los literales de funciones crean instancias de esos rasgos
6) Escriba inferencia. Las reglas para la inferencia de tipos deben ser aumentadas para acomodar la inferencia de los parámetros del tipo de excepción.
Como Scala no ha marcado excepciones, puede punt en todo este asunto
7) Parámetros del tipo de excepción elided para ayudar a la retroadaptación de la transparencia de excepción.
El mismo trato, sin excepciones marcadas.
8) ¿Cómo se forman los literales de clase para los tipos de funciones? ¿Es #void (). Clase? Si es así, ¿cómo funciona si los tipos de objetos se borran? ¿Es #? (?). Clase?
classOf[A => B] //or, equivalently,
classOf[Function1[A,B]]
Tipo de borrado es tipo borrado. Los literales anteriores producen scala.lang.Function1 independientemente de la elección de A y B. Si lo prefiere, puede escribir
classOf[ _ => _ ] // or
classOf[Function1[ _,_ ]]
9) El cargador de clases del sistema debe generar dinámicamente interfaces de tipo de función.
Scala limita arbitrariamente el número de argumentos como máximo a 22 para que no tenga que generar las clases de FunctionN de forma dinámica.
10) ¿Debe la evaluación de una expresión lambda producir un objeto nuevo cada vez?
La especificación de Scala no dice que debe. Pero a partir de 2.8.1, el compilador no optimiza el caso en el que una lambda no captura nada de su entorno. No he probado con 2.9.0 todavía.
Hace algún tiempo, Oracle decidió que agregar Cierres a Java 8 sería una buena idea. Me pregunto cómo se resuelven los problemas de diseño en comparación con Scala, que tenía cierres desde el primer día.
Citando los problemas abiertos de javac.info :
¿Se pueden usar los Mangos de Método para los Tipos de Función? No es obvio cómo hacer que eso funcione. Un problema es que el método maneja los parámetros de tipo reify, pero de una manera que interfiere con la función de subtipado.
¿Podemos deshacernos de la declaración explícita de los parámetros de tipo "throws"? La idea sería usar inferencia de tipo disyuntiva siempre que el límite declarado sea un tipo de excepción comprobado. Esto no es estrictamente compatible con versiones anteriores, pero es poco probable que rompa el código real existente. Sin embargo, probablemente no podamos deshacernos de los "tiros" en el argumento tipo debido a la ambigüedad sintáctica.
No permitir @Shared en las variables de índice de bucle antiguas
Maneje interfaces como Comparator que definen más de un método, todos menos uno serán implementados por un método heredado de Object. La definición de "interfaz con un único método" debería contar solo con métodos que no serían implementados por un método en Object y debería contar múltiples métodos como uno si la implementación de uno de ellos los implementara todos. Principalmente, esto requiere una especificación más precisa de lo que significa para una interfaz tener solo un único método abstracto.
Especifique la asignación de los tipos de funciones a las interfaces: nombres, parámetros, etc. Deberíamos especificar con precisión la asignación de los tipos de funciones a las interfaces generadas por el sistema.
Escriba inferencia. Las reglas para la inferencia de tipos deben ser aumentadas para acomodar la inferencia de los parámetros del tipo de excepción. Del mismo modo, las relaciones de subtipo utilizadas por la conversión de cierre también deberían reflejarse.
Parámetros del tipo de excepción elida para ayudar a la retroadaptación de la transparencia de excepción. Tal vez hacer que los parámetros de tipo de excepción sean el límite. Esto permite la actualización de interfaces genéricas existentes que no tienen un parámetro de tipo para la excepción, como java.util.concurrent.Callable, mediante la adición de un nuevo parámetro de excepción genérico.
¿Cómo se forman los literales de clase para los tipos de funciones? ¿Es #void (). Clase? Si es así, ¿cómo funciona si los tipos de objetos se borran? ¿Es #? (?). Clase?
El cargador de clases del sistema debe generar dinámicamente interfaces de tipo de función. Las interfaces correspondientes a los tipos de funciones se deben generar a petición del cargador de clases de arranque, para que puedan compartirse entre todos los códigos de usuario. Para el prototipo, podemos hacer que javac genere estas interfaces para que el código generado por el prototipo pueda ejecutarse en máquinas virtuales (JDK5-6).
¿Debe la evaluación de una expresión lambda producir un objeto nuevo cada vez? Ojalá no. Si una lambda no captura ninguna variable de un ámbito adjunto, por ejemplo, puede asignarse estáticamente. De manera similar, en otras situaciones una lambda podría moverse de un bucle interno si no captura ninguna de las variables declaradas dentro del bucle. Por lo tanto, sería mejor si la especificación no promete nada acerca de la identidad de referencia del resultado de una expresión lambda, por lo que el compilador puede realizar dichas optimizaciones.
Por lo que yo entiendo, 2., 6. y 7. no son un problema en Scala, porque Scala no usa las Excepciones Controladas como algún tipo de "Shadow type-system" como Java.
¿Qué pasa con el resto?
Me dirigiré solo al número 4 aquí.
Una de las cosas que distingue a los "cierres" de Java de los cierres encontrados en otros lenguajes es que se pueden usar en lugar de la interfaz que no describe una función, por ejemplo, Runnable
. Esto es lo que significa SAM, Método abstracto único.
Java hace esto porque estas interfaces abundan en la biblioteca de Java, y abundan en la biblioteca de Java porque Java se creó sin tipos de funciones o cierres. En su ausencia, cada código que necesitaba inversión de control tuvo que recurrir al uso de una interfaz SAM.
Por ejemplo, Arrays.sort
toma un objeto Comparator
que realizará la comparación entre los miembros de la matriz que se ordenará. Por el contrario, Scala puede ordenar una List[A]
al recibir una función (A, A) => Int
, que se pasa fácilmente a través de un cierre. Ver nota 1 al final, sin embargo.
Por lo tanto, como la biblioteca de Scala se creó para un idioma con tipos de funciones y cierres, no es necesario admitir cierres de SAM en Scala.
Por supuesto, hay una cuestión de interoperabilidad Scala / Java, mientras que la biblioteca de Scala puede no necesitar algo como SAM, la biblioteca Java lo hace. Hay dos formas que se pueden resolver. En primer lugar, como Scala admite cierres y tipos de funciones, es muy fácil crear métodos de ayuda. Por ejemplo:
def runnable(f: () => Unit) = new Runnable {
def run() = f()
}
runnable { () => println("Hello") } // creates a Runnable
En realidad, este ejemplo particular puede hacerse aún más corto mediante el uso de los parámetros de nombre de Scala, pero eso está al lado del punto. De todos modos, esto es algo que, podría decirse, Java podría haber hecho en lugar de lo que va a hacer. Dada la prevalencia de las interfaces SAM, no es tan sorprendente.
La otra forma en que Scala maneja esto es a través de conversiones implícitas. Simplemente anteponiendo implicit
al método runnable
anterior, se crea un método que se aplica automáticamente (nota 2) siempre que se requiera Runnable
pero se proporciona una función () => Unit
.
Los Implicits son muy únicos, sin embargo, y aún son controvertidos hasta cierto punto.
Nota 1 : En realidad, este ejemplo en particular fue elegir con algo de malicia ... Comparator
tiene dos métodos abstractos en lugar de uno, que es todo el problema con él. Dado que uno de sus métodos puede implementarse en términos del otro, creo que simplemente "restarán" los métodos del defensor de la lista abstracta.
Y, en el lado de Scala, aunque hay un método de ordenamiento que usa (A, A) => Boolean
, no (A, A) => Int
, el método de clasificación estándar requiere un objeto Ordering
, que es bastante similar al de Java ¡ Comparator
! En el caso de Scala, sin embargo, Ordering
cumple la función de una clase de tipo .
Nota 2 : los Implicits se aplican automáticamente, una vez que se han importado al alcance .