marzo enero calendario agosto abril java extension-methods language-design closures java-7

enero - ¿Complejidad de la actual propuesta Lambda de Java 7?(Agosto 2010)



calendario enero 2010 (5)

Algunas personas dicen que cada lenguaje de programación tiene su "presupuesto de complejidad" que puede utilizar para cumplir su propósito. Pero si el presupuesto de complejidad se agota, cada cambio menor se vuelve cada vez más complicado y difícil de implementar de una manera compatible con versiones anteriores.

Después de leer la sintaxis provisional actual para Lambda (expressions Expresiones de Lambda, transparencia de excepción, métodos de defensor y referencias de métodos) de agosto de 2010, me pregunto si las personas en Oracle ignoraron completamente el presupuesto de complejidad de Java al considerar tales cambios.

Estas son las preguntas en las que estoy pensando, algunas de ellas más sobre el diseño del lenguaje en general:

  • ¿Las adiciones propuestas son comparables en complejidad a los enfoques elegidos por otros idiomas?
  • ¿Es generalmente posible agregar tales adiciones a un lenguaje y proteger al desarrollador de la complejidad de la implementación?
  • ¿Estas adiciones son un signo de llegar al final de la evolución de Java como lenguaje o es esto esperado cuando se cambia un idioma con una historia enorme?
  • ¿Han tomado otros idiomas un enfoque totalmente diferente en este punto de la evolución del lenguaje?

¡Gracias!


Esto es realmente muy cercano a las funciones Lambda propuestas en la nueva generación de C ++ (C ++ 0x), por lo que creo que los chicos de Oracle han analizado las otras implementaciones antes de preparar las suyas.

http://en.wikipedia.org/wiki/C%2B%2B0x

[](int x, int y) { return x + y; }


Modulo algunas construcciones de alcance-desambiguación, casi todos estos métodos se derivan de la definición real de una abstracción lambda:

λx.E

Para responder a sus preguntas en orden:

No creo que haya ninguna cosa en particular que haga que las propuestas de la comunidad de Java sean mejores o peores que cualquier otra cosa. Como dije, se sigue de la definición matemática, y por lo tanto, todas las implementaciones fieles tendrán casi exactamente la misma forma.

Las funciones anónimas de primera clase conectadas a lenguajes imperativos tienden a terminar como una característica que algunos programadores aman y usan con frecuencia, y que otros ignoran por completo. que eligen ignorar la presencia de esta característica de lenguaje en particular. Creo que ocultar la complejidad y los detalles de la implementación es lo que han intentado hacer utilizando una sintaxis que combina bien con Java, pero que no tiene una connotación real para los programadores de Java.

Probablemente sea conveniente que utilicen algunos bits de sintaxis que no vayan a complicar las definiciones existentes, por lo que están ligeramente limitados en los símbolos que pueden elegir usar como operadores y demás. Ciertamente, la insistencia de Java en permanecer compatible con versiones anteriores limita ligeramente la evolución del lenguaje, pero no creo que esto sea necesariamente algo malo. El enfoque de PHP está en el otro extremo del espectro (es decir, "hey chicos, ¡rompamos todo cada vez que haya un nuevo punto de lanzamiento!"). No creo que la evolución de Java sea intrínsecamente limitada, excepto por algunos de los principios fundamentales de su diseño, por ejemplo, la adhesión a los principios OOP, basados ​​en máquinas virtuales.

Creo que es muy difícil hacer afirmaciones firmes sobre la evolución del lenguaje desde la perspectiva de Java. Está en una posición razonablemente única. Por un lado, es muy, muy popular, pero es relativamente viejo. Microsoft tuvo el beneficio de al menos 10 años de legado de Java antes de que decidieran comenzar a diseñar un lenguaje llamado "C #". El lenguaje de programación C básicamente dejó de evolucionar en absoluto. C ++ ha tenido pocos cambios significativos que encontraron alguna aceptación general. Java ha continuado evolucionando a través de un proceso lento pero constante. Si hay algo, creo que está mejor equipado para seguir evolucionando que cualquier otro idioma con bases de código instaladas de forma similar.


No es mucho más complicado que las expresiones lambda en otros idiomas.

Considerar...

int square(x) { return x*x; }

Java:

#(x){x*x}

Pitón:

lambda x:x*x

DO#:

x => x*x

Creo que el enfoque de C # es un poco más intuitivo. Personalmente preferiría ...

x#x*x


No he seguido el proceso y la evolución de la propuesta de Java 7 lambda, ni siquiera estoy seguro de cuál es la última redacción de la propuesta. Considera esto como una perorata / opinión en lugar de declaraciones de verdad. Además, no he usado Java durante años, por lo que la sintaxis podría estar oxidada e incorrecta en algunos lugares.

Primero, ¿qué son las lambdas al lenguaje Java? Azúcar sintáctica. Mientras que en general, las lambdas habilitan el código para crear pequeños objetos de función en su lugar, ese soporte ya estaba preestablecido, en cierta medida, en el lenguaje Java mediante el uso de clases internas.

Entonces, ¿cuánto mejor es la sintaxis de las lambdas? ¿Dónde supera a las construcciones de lenguaje anteriores? ¿Dónde podría ser mejor?

Para empezar, no me gusta el hecho de que hay dos sintaxis disponibles para las funciones lambda (pero esto va en la línea de C #, por lo que supongo que mi opinión no está muy extendida. Supongo que si queremos adelgazar, entonces #(int x)(x*x) es más dulce que #(int x){ return x*x; } incluso si la doble sintaxis no agrega nada más. Hubiera preferido la segunda sintaxis, más genérica al costo adicional de escribir la return y ; En las versiones cortas.

Para ser realmente útiles, las lambdas pueden tomar variables del alcance en donde se definen y del cierre . Siendo consistentes con las clases internas, las lambdas están restringidas a capturar variables ''efectivamente finales''. La consistencia con las características anteriores del lenguaje es una buena característica, pero para la dulzura, sería bueno poder capturar variables que se pueden reasignar. Para ese propósito, están considerando que las variables presentes en el contexto y anotadas con @Shared serán capturadas por referencia , permitiendo asignaciones. Para mí, esto parece extraño ya que la lambda puede usar una variable en el lugar de la declaración de la variable en lugar de donde se define la lambda. Una sola variable podría usarse en más de un lambda y esto fuerza el mismo comportamiento en todos ellos.

Lambdas intenta simular objetos de funciones reales, pero la propuesta no se completa del todo: para que el analizador sea sencillo, ya que hasta ahora un identificador denota un objeto o un método que se ha mantenido consistente y llamar a lambda requiere el uso de un ! después del nombre lambda: #(int x)(x*x)!(5) devolverá 25 . Esto trae una nueva sintaxis para usar para las lambdas que difieren del resto del idioma, ! dónde ! se destaca de alguna manera como un sinónimo de .execute en una interfaz genérica virtual Lambda<Result,Args...> pero, ¿por qué no hacerlo completo?

Se podría crear una nueva interfaz genérica (virtual) Lambda . Tendría que ser virtual, ya que la interfaz no es una interfaz real, sino una familia de ellas: Lambda<Return> , Lambda<Return,Arg1> , Lambda<Return,Arg1,Arg2> ... Podrían definir una sola ejecución Método, que me gustaría ser como el operator() C ++ operator() , pero si eso es una carga, entonces cualquier otro nombre estaría bien, ¡abrazando el ! Como atajo para la ejecución del método:

interface Lambda<R> { R exec(); } interface Lambda<R,A> { R exec( A a ); }

Entonces el compilador solo necesita traducir identifier!(args) a identifier.exec( args ) , que es simple. La traducción de la sintaxis lambda requeriría que el compilador identifique la interfaz apropiada que se está implementando y podría ser comparada como:

#( int x )(x *x) // translated to new Lambda<int,int>{ int exec( int x ) { return x*x; } }

Esto también permitiría a los usuarios definir clases internas que se pueden usar como lambdas, en situaciones más complejas. Por ejemplo, si la función lambda necesitaba capturar una variable anotada como @Shared de manera de solo lectura, o mantener el estado del objeto capturado en el lugar de captura, la implementación manual de Lambda estaría disponible:

new Lambda<int,int>{ int value = context_value; int exec( int x ) { return x * context_value; } };

De una manera similar a lo que es la definición actual de clases internas, y por lo tanto, es natural para los usuarios actuales de Java. Esto podría usarse, por ejemplo, en un bucle para generar lambdas multiplicadoras:

Lambda<int,int> array[10] = new Lambda<int,int>[10](); for (int i = 0; i < 10; ++i ) { array[i] = new Lambda<int,int>{ final int multiplier = i; int exec( int x ) { return x * multiplier; } }; } // note this is disallowed in the current proposal, as `i` is // not effectively final and as such cannot be ''captured''. Also // if `i` was marked @Shared, then all the lambdas would share // the same `i` as the loop and thus would produce the same // result: multiply by 10 --probably quite unexpectedly. // // I am aware that this can be rewritten as: // for (int ii = 0; ii < 10; ++ii ) { final int i = ii; ... // // but that is not simplifying the system, just pushing the // complexity outside of the lambda.

Esto permitiría el uso de lambdas y métodos que acepten lambdas con la nueva sintaxis simple: #(int x){ return x*x; } #(int x){ return x*x; } o con el enfoque manual más complejo para casos específicos donde el recubrimiento de azúcar interfiere con la semántica deseada.

En general, creo que la propuesta lambda se puede mejorar en diferentes direcciones, que la forma en que agrega azúcar sintáctica es una abstracción con fugas (se ha tratado externamente con problemas que son específicos de la lambda) y que al no proporcionar una interfaz de nivel inferior hace que el código de usuario sea menos legible en casos de uso que no se ajustan perfectamente al caso de uso simple. :


Tal vez esto no sea realmente una respuesta a su pregunta, pero puede ser comparable a la forma en que se extendió por bloques ( examples ) el objetivo-c (que por supuesto tiene una base de usuarios muy estrecha en contraste con Java). Si bien la sintaxis no se ajusta al resto del lenguaje (IMHO), es una adición útil y la complejidad agregada en términos de características del lenguaje se recompensa, por ejemplo, con una menor complejidad de la programación concurrente (cosas simples como la iteración concurrente sobre una matriz o Técnicas complicadas como Grand Central Dispatch ).

Además, muchas tareas comunes son más simples cuando se usan bloques, por ejemplo, hacer que un objeto sea un delegado (o, en la jerga de Java, "escucha") para varias instancias de la misma clase. En Java, las clases anónimas ya se pueden usar para esa causa, por lo que los programadores conocen el concepto y solo pueden ahorrar algunas líneas de código fuente usando expresiones lambda.

En object-c (o los marcos de Cocoa / Cocoa Touch), la nueva funcionalidad a menudo solo es accesible mediante bloques, y parece que los programadores la están adoptando rápidamente (dado que tienen que renunciar a la compatibilidad con versiones anteriores del sistema operativo).