Veo java.util.function.BiFunction, entonces 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 debo agregar que sé que puedo definir mi propia TriFunction, solo estoy tratando de entender la razón detrás de no incluirla en la biblioteca estándar.
Respuestas:
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 forma en que piensas ahora.
Por ejemplo, la función
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 puedes simplemente decir "bueno, deja x: = 1" y probar cada y que quieras probar. Tienes que construir cada vez toda la tupla con
x := 1
. Entonces, si desea ver qué devuelven las funcionesy := 1, y := 2, y := 3
, debe escribirf(1,1) , f(1,2) , f(1,3)
.En Java 8, las funciones constructivas deben manejarse (la mayoría de las veces) mediante el uso de referencias de métodos porque no hay mucha ventaja de usar una función lambda constructiva. Son un poco como métodos estáticos. Puede usarlos, pero no tienen un estado real.
El otro tipo es el destructivo, toma algo y lo desmantela tanto como sea necesario. Por ejemplo, la función destructiva
hace lo mismo que la función
f
que fue constructiva. Los beneficios de una función destructiva son que ahora puede manejar argumentos infinitos, lo que es especialmente conveniente para flujos, y puede dejar los argumentos abiertos. Entonces, si desea ver nuevamente cómo sería el resultado six := 1
yy := 1 , y := 2 , y := 3
, puede decirh = g(1)
yh(1)
es el resultado paray := 1
,h(2)
paray := 2
yh(3)
paray := 3
.¡Así que aquí tienes un estado fijo! Eso es bastante dinámico y eso es la mayoría de las veces lo que queremos de una lambda.
Patrones como Factory son mucho más fáciles si puede poner una función que haga el trabajo por usted.
Los destructivos se combinan fácilmente entre sí. Si el tipo es correcto, puede componerlos como desee. ¡Con eso, puede definir fácilmente morfismos que hacen que las pruebas (con valores inmutables) sean mucho más fáciles!
También puedes hacer eso con una constructiva, 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 que recordar todos los argumentos, que pueden ser muchos.
Entonces, ¿por qué es necesario
BiFunction
que haya más preguntas que por qué noTriFunction
?En primer lugar, muchas veces solo tiene unos pocos valores (menos de 3) y solo necesita un resultado, por lo que una función destructiva normal no sería necesaria en absoluto, una constructiva estaría bien. Y hay cosas como las mónadas que realmente necesitan una función constructiva. Pero aparte de eso, en realidad no hay muchas buenas razones por las que existe un
BiFunction
. ¡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 funciones destructivas, puede encontrarse haciendo muchas cosas con muchas menos líneas de código.
fuente
BiFunction
fue creado para permitir una fácil reducción de datos, y la mayoría deStream
las operaciones de la terminal son solo reducciones de datos. Un buen ejemplo es el que seBinaryOperator<T>
utiliza en muchosCollectors
. Un primer elemento se reduce con el segundo, que luego se puede reducir con el siguiente, y así sucesivamente. Por supuesto, puede crear unFunction<T, Function<T, T>
func = x -> (y -> / * código de reducción aquí * /). ¿Pero en serio? Todo esto cuando simplemente puedes hacerloBinaryOperator<T> func = (x, y) -> /*reduction code here*/
. Además, este enfoque de reducción de datos se parece mucho a su enfoque "destructivo" para mí.Function<Integer,Integer> f = (x,y) -> x + y
es Java válido, que no lo es. ¡Para empezar, debería ser una BiFunción!Si necesita TriFunction, simplemente haga esto:
El siguiente pequeño programa muestra cómo se puede utilizar. Recuerde que el tipo de resultado se especifica como último parámetro de tipo genérico.
Supongo que si hubiera un uso práctico para TriFunction en
java.util.*
ojava.lang.*
se habría definido. Sin embargo, nunca iría más allá de los 22 argumentos ;-) Lo que quiero decir con eso, todo el código nuevo que permite transmitir colecciones nunca requirió TriFunction como ninguno de los parámetros del método. Entonces no fue incluido.ACTUALIZAR
Para completar y seguir la explicación de las funciones destructivas en otra respuesta (relacionada con el curado), así es como se puede emular TriFunction sin una interfaz adicional:
Por supuesto, es posible combinar funciones de otras formas, por ejemplo:
Si bien el curry sería natural para cualquier lenguaje que admita programación funcional más allá de lambdas, Java no está construido de esta manera y, aunque se puede lograr, el código es difícil de mantener y, a veces, de leer. Sin embargo, es muy útil como ejercicio y, a veces, las funciones parciales tienen un lugar legítimo en su código.
fuente
TriFunction<Integer,Integer,Integer,Integer> comp = (x,y,z) -> x + y + z; comp = comp.andThen(s -> s * 2); int result = comp.apply(1, 2, 3); //12
consulte stackoverflow.com/questions/19834611/…andThen()
ejemplo de uso a la respuesta.BiFunction
se usa en laStream
API para realizar la reducción de datos, que se parece mucho al enfoque de currying para mí: nunca tomas más de dos argumentos, y puede procesar cualquier cantidad de elementos, una reducción a la vez (vea mi comentario sobre la respuesta aceptada, me complacería saber si me equivoco al verlo de esa manera).La alternativa es agregar la siguiente dependencia,
Ahora, puede usar la función Vavr, como a continuación hasta 8 argumentos,
3 argumentos:
5 argumentos:
fuente
vavr
biblioteca: hace que la programación de estilo funcional sea lo más soportable posible en Java.Tengo casi la misma pregunta y una respuesta parcial. No estoy seguro de si la respuesta constructiva / deconstructiva es la que tenían en mente los diseñadores del lenguaje. Creo que tener 3 y más hasta N tiene casos de uso válidos.
Vengo de .NET. y en .NET tienes Func y Action para funciones void. También existen predicados y algunos otros casos especiales. Ver: https://msdn.microsoft.com/en-us/library/bb534960(v=vs.110).aspx
Me pregunto cuál fue la razón por la que los diseñadores de lenguajes optaron por Function, Bifunction y no continuaron hasta DecaExiFunction.
La respuesta a la segunda parte es el borrado de tipo. Después de la compilación, no hay diferencia entre Func y Func. Por lo tanto, lo siguiente no se compila:
Las funciones internas se utilizaron para sortear 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 utilizó para evitar conflictos de nombres con el tipo de función java.
Entonces, si desea agregar Func desde 3 hasta 16 argumentos, puede hacer dos cosas.
Ejemplo de la segunda forma:
y
¿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 and then () con 16 argumentos. Eso le daría un error de compilación debido a 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 .NET, las dependencias circulares se eludirían utilizando métodos de extensión que no están presentes en Java.
fuente
andThen
con 16 argumentos? El resultado de una función en Java es un valor único.andThen
toma este valor y hace algo con él. Además, no hay ningún problema con los nombres. Los nombres de las clases deben ser diferentes y estar en diferentes archivos con el mismo nombre, siguiendo la lógica establecida por los desarrolladores del lenguaje Java con Function y BiFunction. Además, todos estos nombres diferentes son necesarios si los tipos de argumentos son diferentes. Se puede crear unVargFunction(T, R) { R apply(T.. t) ... }
solo tipo.Encontré el código fuente de BiFunction aquí:
https://github.com/JetBrains/jdk8u_jdk/blob/master/src/share/classes/java/util/function/BiFunction.java
Lo modifiqué para crear TriFunction. Como BiFunction, utiliza andThen () y not compose (), por lo que para algunas aplicaciones que requieren compose (), puede que no sea apropiado. Debería estar bien para tipos de objetos normales. Un buen artículo sobre andThen () y compose () se puede encontrar aquí:
http://www.deadcoderising.com/2015-09-07-java-8-functional-composition-using-compose-and-andthen/
fuente
También puede crear su propia función tomando los 3 parámetros
fuente
No siempre puede detenerse en TriFunction. A veces, es posible que deba pasar un número n de parámetros a sus funciones. Luego, el equipo de soporte tendrá que crear una QuadFunction para corregir su código. La solución a largo plazo sería crear un Objeto con los parámetros adicionales y luego usar la Función o BiFunción lista para usar.
fuente