util predefined interfaces functions functional java lambda java-8

predefined - java functions



Java 8: ¿Dónde está TriFunction(y kin) en java.util.function? ¿O cuál es la alternativa? (3)

Hasta donde yo sé, solo hay dos tipos de funciones, destructivas y constructivas.

Mientras que la función constructiva, como su nombre lo indica, construye algo, una destructiva destruye algo, pero no de la manera que usted puede pensar ahora.

Por ejemplo, la función

Function<Integer,Integer> f = (x,y) -> x + y

es constructivo Como necesitas construir algo. En el ejemplo, construiste la tupla (x, y) . Las funciones constructivas tienen el problema de no poder manejar argumentos infinitos. Pero lo peor es que no puedes dejar una discusión abierta. No puede simplemente decir "bueno, deje x: = 1" y pruebe cada y que quiera probar. Tienes que construir cada vez la tupla completa con x := 1 . Entonces, si te gusta ver qué devuelven las funciones para y := 1, y := 2, y := 3 tienes que escribir f(1,1) , f(1,2) , f(1,3) .

En Java 8, las funciones constructivas deben manejarse (la mayoría de las veces) utilizando referencias de métodos porque no hay mucha ventaja de usar una función constructiva lambda. Son un poco como métodos estáticos. Puedes usarlos, pero no tienen un estado real.

El otro tipo es destructivo, toma algo y lo desmantela todo lo que necesita. Por ejemplo, la función destructiva

Function<Integer, Function<Integer, Integer>> g = x -> (y -> x + y)

hace lo mismo que la función f que fue constructiva. Los beneficios de una función destructiva son que puede manejar argumentos infinitos, lo que es especialmente conveniente para las transmisiones, y puede dejar los argumentos abiertos. Entonces, si quieres ver de nuevo cómo sería el resultado si x := 1 e y := 1 , y := 2 , y := 3 , puedes decir h = g(1) y h(1) es el resultado para y := 1 , h(2) para y := 2 y h(3) para y := 3 .

¡Aquí tienes un estado fijo! Eso es bastante dinámico y eso es la mayoría de las veces lo que queremos de un lambda.

Patrones como Factory son mucho más fáciles si solo puede poner una función que hace el trabajo por usted.

Los destructivos se combinan fácilmente entre sí. Si el tipo es correcto, puede componerlos como desee. Usando eso, puedes definir fácilmente los morfismos que hacen que (con valores inmutables) las pruebas sean mucho más fáciles.

Puedes hacer eso también con uno constructivo, pero la composición destructiva se ve mejor y más como una lista o un decorador, y la constructiva se parece mucho a un árbol. Y cosas como retroceder con funciones constructivas simplemente no son agradables. Puede simplemente guardar las funciones parciales de una destructiva (programación dinámica), y en "retroceder" simplemente use la antigua función destructiva. Eso hace que el código sea mucho más pequeño y mejor legible. Con funciones constructivas tienes más o menos para recordar todos los argumentos, que pueden ser muchos.

Entonces, ¿por qué hay una necesidad de BiFunction debería ser más una pregunta que por qué no hay TriFunction ?

Antes que nada, muchas veces solo tienes unos pocos valores (menos de 3) y solo necesitas un resultado, por lo que una función destructiva normal no sería necesaria en absoluto, una constructiva funcionaría bien. Y hay cosas como mónadas que realmente necesitan una función constructiva. Pero aparte de eso, en realidad no hay muchas buenas razones por las que haya una BiFunction en absoluto. ¡Lo que no significa que deba eliminarse! Lucho por mis Mónadas hasta que muera!

Entonces, si tiene muchos argumentos, que no puede combinar en una clase de contenedor lógico, y si necesita que la función sea constructiva, use una referencia de método. De lo contrario, intente utilizar la nueva capacidad adquirida de las funciones destructivas, puede encontrarse haciendo muchas cosas con muchas menos líneas de código.

Veo java.util.function.BiFunction, así que puedo hacer esto:

BiFunction<Integer, Integer, Integer> f = (x, y) -> { return 0; };

¿Qué pasa si eso no es lo suficientemente bueno y necesito TriFunction? ¡No existe!

TriFunction<Integer, Integer, Integer, Integer> f = (x, y, z) -> { return 0; };

Supongo que debería agregar que sé que puedo definir mi propia TriFunction, solo estoy tratando de comprender la razón detrás de no incluirla en la biblioteca estándar.


Si necesita TriFunction, simplemente haga esto:

@FunctionalInterface interface TriFunction<A,B,C,R> { R apply(A a, B b, C c); default <V> TriFunction<A, B, C, V> andThen( Function<? super R, ? extends V> after) { Objects.requireNonNull(after); return (A a, B b, C c) -> after.apply(apply(a, b, c)); } }

El siguiente pequeño programa muestra cómo se puede usar. Recuerde que el tipo de resultado se especifica como un último parámetro de tipo genérico.

public class Main { public static void main(String[] args) { BiFunction<Integer, Long, String> bi = (x,y) -> ""+x+","+y; TriFunction<Boolean, Integer, Long, String> tri = (x,y,z) -> ""+x+","+y+","+z; System.out.println(bi.apply(1, 2L)); //1,2 System.out.println(tri.apply(false, 1, 2L)); //false,1,2 tri = tri.andThen(s -> "["+s+"]"); System.out.println(tri.apply(true,2,3L)); //[true,2,3] } }

Supongo que si hubiera un uso práctico para TriFunction en java.util.* O java.lang.* Se habría definido. Sin embargo, nunca iría más allá de 22 argumentos ;-) Lo que quiero decir con eso, todo nuevo código que permite transmitir colecciones nunca requirió TriFunction como ninguno de los parámetros del método. Entonces no estaba incluido.

ACTUALIZAR

Para completar y seguir la explicación de las funciones destructivas en otra respuesta (relacionada con el currying), así es como se puede emular TriFunction sin interfaz adicional:

Function<Integer, Function<Integer, UnaryOperator<Integer>>> tri1 = a -> b -> c -> a + b + c; System.out.println(tri1.apply(1).apply(2).apply(3)); //prints 6

Por supuesto, es posible combinar funciones de otras maneras, por ejemplo:

BiFunction<Integer, Integer, UnaryOperator<Integer>> tri2 = (a, b) -> c -> a + b + c; System.out.println(tri2.apply(1, 2).apply(3)); //prints 6 //partial function can be, of course, extracted this way UnaryOperator partial = tri2.apply(1,2); //this is partial, eq to c -> 1 + 2 + c; System.out.println(partial.apply(4)); //prints 7 System.out.println(partial.apply(5)); //prints 8

Si bien currying sería natural para cualquier lenguaje que soporte programación funcional más allá de lambdas, Java no está construido de esta manera y, aunque alcanzable, el código es difícil de mantener y, a veces, de leer. Sin embargo, es muy útil como ejercicio, y algunas veces las funciones parciales tienen un lugar legítimo en tu código.


Tengo casi la misma pregunta y una respuesta parcial. No estoy seguro de si la respuesta constructiva / deconstructiva es lo que los diseñadores de idiomas tenían en mente. Creo que tener 3 y más hasta N tiene casos de uso válidos.

Vengo de .NET. y en .NET tiene Func y Action para funciones nulas. Predicado y algunos otros casos especiales también existen. Ver: https://msdn.microsoft.com/en-us/library/bb534960(v=vs.110).aspx

Me pregunto cuál fue la razón por la cual los diseñadores de idiomas optaron por Function, Bifunction y no continuaron hasta DecaExiFunction.

La respuesta a la segunda parte es tipo borrado. Después de la compilación, no hay diferencia entre Func y Func. Por lo tanto, el siguiente no compila:

package eu.hanskruse.trackhacks.joepie; public class Functions{ @FunctionalInterface public interface Func<T1,T2,T3,R>{ public R apply(T1 t1,T2 t2,T3 t3); } @FunctionalInterface public interface Func<T1,T2,T3,T4,R>{ public R apply(T1 t1,T2 t2,T3 t3, T4 t4); } }

Las funciones internas se usaron para eludir otro problema menor. Eclipse insistió en tener ambas clases en archivos llamados Función en el mismo directorio ... No estoy seguro de si esto es un problema del compilador hoy en día. Pero no puedo convertir el error en Eclipse.

Func se usó para evitar conflictos de nombres con el tipo de función java.

Entonces, si desea agregar Func de 3 a 16 argumentos, puede hacer dos cosas.

  • Haga TriFunc, TesseraFunc, PendeFunc, ... DecaExiFunc, etc.
    • (¿Debo usar griego o latín?)
  • Use nombres o clases de paquetes para hacer que los nombres sean diferentes.

Ejemplo para la segunda forma:

package eu.hanskruse.trackhacks.joepie.functions.tri; @FunctionalInterface public interface Func<T1,T2,T3,R>{ public R apply(T1 t1,T2 t2,T3 t3); }

y

package eu.trackhacks.joepie.functions.tessera; @FunctionalInterface public interface Func<T1,T2,T3,T4,R>{ public R apply(T1 t1,T2 t2,T3 t3, T4 t4); }

¿Cuál sería el mejor enfoque?

En los ejemplos anteriores, no incluí implementaciones para los métodos andThen () y compose (). Si agrega estos debe agregar 16 sobrecargas cada uno: el TriFunc debe tener un andthen () con 16 argumentos. Eso le daría un error de compilación debido a las dependencias circulares. Además, no tendría estas sobrecargas para Function y BiFunction. Por lo tanto, también debe definir Func con un argumento y Func con dos argumentos. En las dependencias circulares de .NET se eludiría mediante el uso de métodos de extensión que no están presentes en Java.