java lambda devolviendo una lambda

Estoy tratando de hacer lo que parece ser una cosa relativamente básica en la nueva jdk8 land de la progtwigción funcional, pero no puedo hacer que funcione. Tengo este código de trabajo:

import java.util.*; import java.util.concurrent.*; import java.util.stream.*; public class so1 { public static void main() { List l = new ArrayList(Arrays.asList(1, 2, 3)); List<Callable> checks = l.stream(). map(n -> (Callable) () -> { System.out.println(n); return null; }). collect(Collectors.toList()); } } 

Toma una lista de números y produce una lista de funciones que pueden imprimirlos. Sin embargo, el lanzamiento explícito a Callable parece redundante. Nos parece a mí y a IntelliJ. Y ambos estamos de acuerdo en que esto también debería funcionar:

 List<Callable> checks = l.stream(). map(n -> () -> { System.out.println(n); return null; }). collect(Collectors.toList()); 

Sin embargo, recibo un error:

 so1.java:10: error: incompatible types: cannot infer type-variable(s) R List<Callable> checks = l.stream().map(n -> () -> {System.out.println(n); return null;}).collect(Collectors.toList()); ^ (argument mismatch; bad return type in lambda expression Object is not a functional interface) where R,T are type-variables: R extends Object declared in method map(Function) T extends Object declared in interface Stream 1 error 

Llegas a una limitación de la tipificación de objectives de Java 8 que se aplica al receptor de una invocación a un método. Si bien la tipificación de objectives funciona (la mayoría de las veces) para los tipos de parámetros, no funciona para el objeto o la expresión en la que se invoca el método.

Aquí, l.stream(). map(n -> () -> { System.out.println(n); return null; }) l.stream(). map(n -> () -> { System.out.println(n); return null; }) es el destinatario de la invocación del método collect(Collectors.toList()) , por lo que el tipo de destino List> no es considerado para eso.

Es fácil probar que las expresiones lambda anidadas funcionan si el tipo de destino es conocido, por ejemplo

 static  Function> toCallable() { return n -> () -> { System.out.println(n); return null; }; } 

funciona sin problemas y puede usarlo para resolver su problema original como

 List> checks = l.stream() .map(toCallable()).collect(Collectors.toList()); 

También puede resolver el problema introduciendo un método auxiliar que cambia el rol de la primera expresión del receptor del método a un parámetro

 // turns the Stream s from receiver to a parameter static  R collect(Stream s, Collector< ? super T, A, R> collector) { return s.collect(collector); } 

y reescribir la expresión original como

 List> checks = collect(l.stream().map( n -> () -> { System.out.println(n); return null; }), Collectors.toList()); 

Esto no reduce la complejidad del código, pero puede comstackrse sin problemas. Para mí, es un déjà vu. Cuando salieron Java 5 y Generics, los progtwigdores tuvieron que repetir los parámetros de tipo en las new expresiones, mientras que simplemente envolviendo la expresión en un método genérico demostró que inferir el tipo no es un problema. Hubo que esperar hasta que Java 7 permitió a los progtwigdores omitir esta repetición innecesaria de los argumentos de tipo (utilizando el “operador de diamante”). Ahora que tenemos una situación similar, envolver una expresión de invocación en otro método, convirtiendo el receptor en un parámetro, prueba que esta limitación es innecesaria. Entonces quizás nos deshagamos de esta limitación en Java 10 …

Me encontré con este mismo problema y pude resolverlo especificando explícitamente el parámetro de tipo genérico para map así:

 List> checks = l.stream(). >map(n -> () -> { System.out.println(n); return null; }). collect(Collectors.toList()); 

Todavía no me he adentrado en las reglas exactas de cómo funciona la inferencia de tipos con lambdas. Sin embargo, desde el punto de vista del diseño general del lenguaje, no siempre es posible escribir reglas de lenguaje que permitan al comstackdor descubrir todo lo que creemos que debería ser. He sido un mantenedor de comstackdores para un comstackdor de lenguaje Ada y estoy familiarizado con muchos de los problemas de diseño de idiomas allí. Ada utiliza la inferencia de tipo en muchos casos (donde el tipo de una construcción no puede determinarse sin mirar toda la expresión que contiene la construcción, que creo que es el caso con esta expresión Java lambda también). Existen algunas reglas de lenguaje que hacen que los comstackdores rechacen algunas expresiones como ambiguas cuando, en teoría, solo existe una interpretación posible. Una razón, si mal no recuerdo, es que alguien encontró un caso donde una regla que hubiera permitido al comstackdor descubrir la interpretación correcta habría requerido que el comstackdor hiciera 17 pasadas a través de una expresión para interpretarla correctamente.

Entonces, si bien podemos pensar que un comstackdor “debería” ser capaz de resolver algo en un caso particular, simplemente puede ser inviable.