Razones para eliminar tipos de funciones en Java 8

12

He estado tratando de entender por qué el JDK 8 Lambda Expert Group (EG) decidió no incluir un nuevo tipo de función en el lenguaje de programación Java.

Al repasar la lista de correo encontré un hilo con la discusión sobre la eliminación de los tipos de función .

Muchas de las declaraciones son ambiguas para mí, tal vez por la falta de contexto y, en algunos casos, por mi conocimiento limitado sobre la implementación de sistemas de tipos.

Sin embargo, hay un par de preguntas que creo que puedo formular de manera segura en este sitio para ayudarme a comprender mejor lo que significan.

Sé que podría hacer las preguntas en la lista de correo, pero el hilo es viejo y todas las decisiones ya están tomadas, por lo que es probable que me ignoren, sobre todo viendo que estos tipos ya se están retrasando con sus planes.

En su respuesta para apoyar la eliminación de los tipos de función y respaldar el uso de los tipos de SAM, Brian Goetz dice:

Sin reificación. Hubo un largo hilo sobre lo útil que sería reificar los tipos de función. Sin reificación, los tipos de funciones se ven afectados.

No pude encontrar el hilo que menciona. Ahora, puedo entender que la introducción de un tipo de función estructural puede implicar ciertas complicaciones en el sistema de tipos mayormente nominal de Java, lo que no puedo entender es cómo los tipos SAM parametrizados son diferentes en términos de reificación.

¿No están ambos sujetos a los mismos problemas de reificación? ¿Alguien entiende cómo los tipos de funciones son diferentes a los tipos SAM parametrizados en términos de reificación?

En otro comentario, Goetz dice:

Hay dos enfoques básicos para escribir: nominal y estructural. La identidad de un nominal se basa en su nombre; la identidad de un tipo estructural se basa en lo que está compuesto (como "tupla de int, int" o "función de int a flotante"). La mayoría de los idiomas escogen principalmente nominal o mayormente estructural; no hay muchos lenguajes que combinen con éxito la tipificación nominal y estructural, excepto "alrededor de los bordes". Java es casi completamente nominal (con algunas excepciones: las matrices son de tipo estructural, pero en la parte inferior siempre hay un tipo de elemento nominal; los genéricos también tienen una mezcla de nominal y estructural, y esto es, de hecho, parte de la fuente de muchos de las quejas de la gente sobre los genéricos.) Injerto de un sistema de tipo estructural (tipos de función) en Java ' s sistema de tipo nominal significa nueva complejidad y casos extremos. ¿Vale la pena el beneficio de los tipos de función?

Aquellos de ustedes con experiencia en la implementación de sistemas de tipos. ¿Conoces algunos ejemplos de estas complejidades o casos extremos que él menciona aquí?

Honestamente, me confunden estas acusaciones cuando considero que un lenguaje de programación como Scala, que se basa completamente en la JVM, tiene soporte para tipos estructurales como funciones y tuplas, incluso con los problemas de reificación de la plataforma subyacente.

No me malinterpreten, no estoy diciendo que un tipo de función debería ser mejor que los tipos de SAM. Solo quiero entender por qué tomaron esta decisión.

edalorzo
fuente
44
"Injertar un sistema de tipo estructural (tipos de función) en el sistema de tipo nominal de Java significa una nueva complejidad" , lo más probable es que se trate de complejidades para los usuarios que tienen que aprender más para usar el lenguaje . Algo como Java simple y fácil de usar se convierte en C ++ complejo y difícil de aprender , cosas así. Los miembros de la lista de correo probablemente podrían aludir a las críticas de la "gran ola de mejoras" anterior, un ejemplo destacado es Java 5 es un artículo de Clinton Begin de MyBatis
mosquito
3
"Java simple y fácil de usar se vuelve complejo y difícil de aprender C ++": Desafortunadamente, un problema con los lenguajes de programación convencionales es que tienen que mantener la compatibilidad con versiones anteriores y, por lo tanto, tienden a volverse demasiado complejos. Entonces, en mi humilde opinión (con muchos años de experiencia con C ++ y Java) hay algo fiel a esta afirmación. A menudo tengo la sensación de que Java debería congelarse y, en su lugar, debería desarrollarse un nuevo lenguaje. Pero Oracle no puede correr ese riesgo.
Giorgio el
44
@edalorzo: Como mostraron Clojure, Groovy, Scala y otros lenguajes, es posible (1) definir un nuevo lenguaje (2) usar todas las bibliotecas y herramientas que ya están escritas en Java sin romper la compatibilidad con versiones anteriores (3) Los programadores de Java tienen la libertad de elegir si quieren quedarse con Java, cambiar a otro idioma o usar ambos. En mi opinión, expandir un idioma existente indefinidamente es una estrategia para mantener a los clientes, de la misma manera que las compañías de telefonía móvil necesitan crear nuevos modelos todo el tiempo.
Giorgio el
2
Como entendí de SAMbdas en Java , una de las razones es que sería problemático mantener algunas bibliotecas (colecciones) compatibles con versiones anteriores.
Petr Pudlák
2
Publicación relacionada en SO que enlaza con esta otra publicación de Goetz .
Assylias

Respuestas:

6

El enfoque SAM es en realidad algo similar a lo que Scala (y C ++ 11) hacen con funciones anónimas (creadas con el =>operador de Scala o la []()sintaxis de C ++ 11 (lambda)).

La primera pregunta a responder en el lado de Java es si el tipo de retorno de una declaración lambda es un nuevo tipo primitve, like into byte, o un tipo de objeto de algún tipo. En Scala, allí son no hay tipos primitivos - incluso un número entero es un objeto de la clase Int- y las funciones no son diferentes que son objetos de la clase, Function1, Function2, y así sucesivamente, dependiendo del número de los argumentos de la función toma.

C ++ 11, ruby ​​y python tienen expresiones lambda que devuelven un objeto invocable de manera explícita o implícita. El objeto devuelto tiene algún método estándar (como el #callque se puede usar para llamarlo como una función. C ++ 11, por ejemplo, usa un std::functiontipo que se sobrecarga operator()para que las llamadas del método de llamada del objeto incluso parezcan llamadas de función, textualmente. :-)

Donde la nueva propuesta para Java se vuelve desordenada es en el uso de la tipificación estructural para permitir asignar dicho método a otro objeto, como el Comparatorque tiene un único método principal con un nombre diferente . Si bien esto es conceptualmente repulsivo en cierto modo, lo hace significa que el objeto resultante puede suministrarse a las funciones existentes que tienen un objeto para representar una devolución de llamada, un comparador, y así sucesivamente, y espera ser capaz de llamar a un único bien definido método como #compareo #callback. El truco de C ++ de anular operator()cuidadosamente esquivó este problema incluso antes de C ++ 11, ya que todas esas opciones de devolución de llamada se podían llamar de la misma manera, y por lo tanto, el STL no requería ningún ajuste para permitir el uso de lambdas de C ++ 11 consorty así. Java, al no haber utilizado un nombre estándar para tales objetos en el pasado (tal vez porque ningún truco como la sobrecarga del operador hizo obvio un enfoque único), no es tan afortunado, por lo que este truco les impide tener que cambiar una gran cantidad de API existentes .

jimwise
fuente
1
Irónicamente, el problema de tener una miríada de tipos diferentes e incompatibles, todos ellos representando algún tipo de "cosa similar a una función", podría haberse evitado de manera clara al agregar tipos de funciones estándar a la biblioteca desde el principio, independientemente de tener una sintaxis literal real para construirlos. Si hubiera habido un java.util.Function2<T1, T2, R>en Java 1.0, entonces no habría Comparatorinterfaz, en cambio todos esos métodos tomarían un Function2<T1, T2, int>. (Bueno, habría una gran cantidad de interfaces, como Function2TT_int<T1, T2>por las primitivas, pero entiendes mi punto.)
Jörg W Mittag, el