Java 8: Where is the TriFunction (and gender) in java.util.function? Or what is the alternative?

I see java.util.function.BiFunction, so I can do this:

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

What if it's not good enough and I need TriFunction? This does not exist!

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

I think I should add that I know that I can define my own TriFunction, I'm just trying to understand the rationale that does not include it in the standard library.

+90
java lambda java-8
Aug 23 '13 at 10:11
source share
6 answers

As far as I know, there are only two kinds of functions: destructive and constructive.

While a constructive function, as the name implies, creates something, destructive destroys something, but not in the way you can think now.

For example, the function

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

is constructive . How do you need to build something. In the example, you built a tuple (x, y). Constructive functions have a problem, inability to handle infinite arguments. But the worst thing: you cannot just leave the argument open. You can't just say โ€œok, let x: = 1โ€ and try every y you can try. You have to build every time the whole tuple with x := 1 . Therefore, if you like to see which functions are returned for y := 1, y := 2, y := 3 , you need to write f(1,1) , f(1,2) , f(1,3) .

In Java 8, constructive functions should be handled (most of the time) using method references, because there are not many advantages to using a constructive lambda function. They are a bit like static methods. You can use them, but they have no real state.

Another type is destructive, it requires something and dismantles it as necessary. For example, destructive function

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

does the same as the constructive function f . Advantages of the destructive function: you can now handle endless arguments, which is especially convenient for threads, and you can just leave the arguments open. Therefore, if you want to know again what the result will look like if x := 1 and y := 1 , y := 2 , y := 3 , you can say h = g(1) and h(1) is the result for y := 1 , h(2) for y := 2 and h(3) for y := 3 .

So you have a fixed state! This is quite dynamic and in most cases what we want from lambda.

Templates like Factory are much simpler if you can just add a function that does your job.

Destructive easily combined. If the type of rights is right, you can simply compose them as you wish. Using this, you can easily identify morphisms that make (with immutable values) testing much easier!

You can do it too with a constructive one, but a destructive composition looks better and looks more like a list or a decorator, and a constructive one looks very much like a tree. And things like rollback with design features are just not nice. You can just save the partial functions of the destructive (dynamic programming), and on the "backtrack" just use the old destructive function. This makes the code much smaller and easier to read. With constructive functions, you more or less remember all the arguments, which can be many.

So, why is there a need for BiFunction , which should be more problematic than why there is no TriFunction ?

Firstly, a lot of time you have only a few values โ€‹โ€‹(less than 3), and you only need the result, so a normal destructive function will not be needed at all, a constructive one will be fine. And there are things like monads that really need a constructive function. But beyond that, there really are no good reasons why BiFunction exists BiFunction all. This does not mean that you need to delete it! I fight for my monads until I die!

So, if you have many arguments that you cannot combine into a class of logical containers, and if you need a function to be constructive, use the method reference. Otherwise, try to use the new acquired ability of destructive functions, you may find that you are doing many things with much fewer lines of code.

+70
Feb 15 '14 at 1:07
source share

If you need TriFunction, just do the following:

 @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)); } } 

The following small program shows how it can be used. Remember that the result type is specified as the last typical type parameter.

  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] } } 

I suppose there would be a practical application for TriFunction in java.util.* Or java.lang.* , That would be defined. I would never go beyond 22 arguments, though ;-) What do I mean by this, the new code that allows streaming collections never required TriFunction like any of the method parameters. Therefore, it was not included.

UPDATE

For completeness and after explaining destructive functions in another answer (related to currying), here is how TriFunction can be emulated without an additional interface:

 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 

Of course, you can combine functions in other ways, for example:

 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 

While currying would be natural for any language that supports functional programming outside of lambdas, Java is not built in this way and, although achievable, the code is difficult to maintain and sometimes read. However, this is very useful as an exercise, and sometimes partial functions have their rightful place in your code.

+122
Oct 29 '13 at 4:07 on
source share

I have almost the same question and partial answer. Not sure if the constructive / deconstructive answer was what the language developers were talking about. I think that 3 or more up to N have valid use cases.

I came from .NET. and in .NET you have Func and Action functions for void. There are also predicate and some other special cases. See: https://msdn.microsoft.com/en-us/library/bb534960(v=vs.110).aspx

I wonder what is the reason why the language designers chose Function, B Function and did not continue until DecaExiFunction?

The answer to the second part is type erasure. After compilation, there is no difference between Func and Func. Therefore, the following does not compile:

 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); } } 

Internal functions were used to work around another minor issue. Eclipse insisted on having both classes in files named Function in the same directory ... Not sure if this is a compiler problem right now. But I can not turn a mistake in Eclipse.

Func was used to prevent name clashes with the java function type.

So, if you want to add Func from 3 to 16 arguments, you can do two things.

  • Make TriFunc, TesseraFunc, PendeFunc, ... DecaExiFunc, etc.
    • (Should I use Greek or Latin?)
  • Use package names or classes to make names different.

An example for the second method:

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

and

 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); } 

What would be the best approach?

In the above examples, I did not include implementations of the andThen () and compose () methods. If you add them, you must add 16 overloads: TriFunc should have andthen () with 16 arguments. This will give you a compilation error due to circular dependencies. Also you would not have these overloads for function and BiFunction. Therefore, you should also define Func with one argument and Func with two arguments. In circular dependencies, .NET can be circumvented using extension methods that are not available in Java.

+7
Oct. 17 '16 at 17:28
source share

An alternative is to add the dependency below,

 <dependency> <groupId>io.vavr</groupId> <artifactId>vavr</artifactId> <version>0.9.0</version> </dependency> 

Now you can use the Vavr function, as shown below, with up to 8 arguments,

3 arguments:

 Function3<Integer, Integer, Integer, Integer> f = (a, b, c) -> a + b + c; 

5 arguments:

 Function5<Integer, Integer, Integer, Integer, Integer, Integer> f = (a, b, c, d, e) -> a + b + c + d + e; 
+5
Jan 21 '19 at 7:23
source share

I found the source code for BiFunction here:

https://github.com/JetBrains/jdk8u_jdk/blob/master/src/share/classes/java/util/function/BiFunction.java

I modified it to create a TriFunction. Like BiFunction, it uses andThen (), not compose (), so for some applications that require compose (), this may not be appropriate. This should be good for normal kinds of objects. A good article on andThen () and compose () can be found here:

http://www.deadcoderising.com/2015-09-07-java-8-functional-composition-using-compose-and-andthen/

 import java.util.Objects; import java.util.function.Function; /** * Represents a function that accepts two arguments and produces a result. * This is the three-arity specialization of {@link Function}. * * <p>This is a <a href="package-summary.html">functional interface</a> * whose functional method is {@link #apply(Object, Object)}. * * @param <S> the type of the first argument to the function * @param <T> the type of the second argument to the function * @param <U> the type of the third argument to the function * @param <R> the type of the result of the function * * @see Function * @since 1.8 */ @FunctionalInterface public interface TriFunction<S, T, U, R> { /** * Applies this function to the given arguments. * * @param s the first function argument * @param t the second function argument * @param u the third function argument * @return the function result */ R apply(S s, T t, U u); /** * Returns a composed function that first applies this function to * its input, and then applies the {@code after} function to the result. * If evaluation of either function throws an exception, it is relayed to * the caller of the composed function. * * @param <V> the type of output of the {@code after} function, and of the * composed function * @param after the function to apply after this function is applied * @return a composed function that first applies this function and then * applies the {@code after} function * @throws NullPointerException if after is null */ default <V> TriFunction<S, T, U, V> andThen(Function<? super R, ? extends V> after) { Objects.requireNonNull(after); return (S s, T t, U u) -> after.apply(apply(s, t, u)); } } 
+1
Nov 28 '18 at 20:17
source share

You can also create your own function by accepting 3 parameters

 @FunctionalInterface public interface MiddleInterface<F,T,V>{ boolean isBetween(F from, T to, V middleValue); } MiddleInterface<Integer, Integer, Integer> middleInterface = (x,y,z) -> x>=y && y<=z; // true 
0
May 19 '19 at 11:26
source share



All Articles