And yes, there is definitely use for BiFunction, TriFunction, ... Otherwise people would not search for it. It guess the whole lambda thing is just too new for Oracle right now and will be extended in later Java versions. At the moment it's more a proof of concept.
Interface BiFunction<T,U,R> This is a functional interface and can therefore be used as the assignment target for a lambda expression or method reference. Represents a function that accepts two arguments and produces a result. This is the two-arity specialization of Function .
Predicate is a functional interface in Java that accepts a single input and can return a boolean value. BiPredicate is a functional interface in Java that accepts two inputs and can return a boolean value. Predicate and BiPredicate are usually used to apply in a filter for a collection of objects.
Interface TriFunction<T,U,V,R> Represents a function that accepts three arguments and produces a result. This is the three-arity specialization of Function . This is a functional interface whose functional method is apply(Object, Object, Object) .
If you need TriFunction, just do this:
@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));
}
}
Following small program shows how it can be used. Remember that result type is specified as a last generic 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 guess if there was practical use for TriFunction in java.util.*
or java.lang.*
it would have been defined. I would never go beyond 22 arguments, though ;-) What I mean by that, all new code that allows to stream collections never required TriFunction as any of the method parameters. So it was not included.
UPDATE
For completeness and following the destructive functions explanation in another answer (related to currying), here is how TriFunction can be emulated without 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, it is possible to combine functions in other ways, e.g.:
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 to any language that supports functional programming beyond lambdas, Java is not built this way and, while achievable, the code is hard to maintain, and sometimes read. However, it is very helpful as an exercise, and sometimes partial functions have a rightful place in your code.
As far as I know, there are only two kinds of functions, destructive and constructive.
While constructive function, as the name implies, constructs something, a destructive one destroys something, but not in the way you may think now.
For example, the function
Function<Integer,Integer> f = (x,y) -> x + y
is a constructive one.
As you need to construct something. In the example
you constructed the tuple (x,y). Constructive functions have the problem,
of being not able to handle infinite arguments. But the worst thing is, you
can't just leave an argument open. You can't just say "well, let x := 1" and try out
every y you may like to try. You have to construct every time the whole tuple with
x := 1
. So if you like to see what the functions return for y := 1, y := 2, y := 3
you
have to write f(1,1) , f(1,2) , f(1,3)
.
In Java 8, constructive functions should be handled (most of the time) by using method references because there's not much advantage of using a constructive lambda function. They are a bit like static methods. You can use them, but they have no real state.
The other type is the destructive one, it takes something and dismantles it as far as needed. For example, the destructive function
Function<Integer, Function<Integer, Integer>> g = x -> (y -> x + y)
does the same as the function f
which was constructive. The benefits of a destructive function are, you
can handle now infinite arguments, which is especially convenient for streams, and you can just leave arguments open.
So if you again want to see what would the result be 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 here you have a fixed state! That's quite dynamic and that's most of the time that what we want from a lambda.
Patterns like Factory are a lot easier if you can just put in a function which does the work for you.
Destructive ones are easily combined with each other. If the type is right you can just compose them as you like. Using that, you can easily define morphisms which make (with immutable values) testing a lot easier!
You can do that too with a constructive one, but destructive composition looks nicer and more like a list or a decorator, and the constructive one looks a lot like a tree. And things like backtracking with constructive functions are just not nice. You can just save the partial functions of a destructive one (dynamic programming), and on "backtrack" just use the old destructive function. That makes code a lot smaller and better readable. With constructive functions you have more or less to remember all arguments, which can be a lot.
So why is there a need for BiFunction
should be more of question than why there is no TriFunction
?
First of all, a lot of time you just have a few values (less than 3) and need just a result, so a normal destructive function would not be needed at all, a constructive one would do fine. And there are things like monads which
really needs a constructive function. But aside from that, there are not really a lot of good reasons why there is a BiFunction
at all. Which doesn't mean it should be removed! I fight for my Monads until I die!
So if you have a lot of arguments, which you can't combine into a logical container class, and if you need the function to be constructive, use a method reference. Otherwise try to use the new gained ability of destructive functions, you may find yourself doing a lot of things with a lot less code lines.
Alternative is, add the below dependency,
<dependency>
<groupId>io.vavr</groupId>
<artifactId>vavr</artifactId>
<version>0.9.0</version>
</dependency>
Now, you can use Vavr Function, like below upto 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;
I Have almost the same question and a partial answer. Not sure whether the constructive/deconstructive answer is what the language designers had in mind. I think having 3 and more upto N has valid use cases.
I come from .NET. and in .NET you have Func and Action for void functions. Predicate and some other special cases also exist. See: https://msdn.microsoft.com/en-us/library/bb534960(v=vs.110).aspx
I wonder what the reason was why the language designers opted for Function, Bifunction 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. The following therefore 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);
}
}
Inner functions were used to circumvent another minor problem. Eclipse insisted on having both classes in files named Function in the same directory... Not sure whether this a compiler issue nowadays. But I cannot turn the error of in Eclipse.
Func was used to prevent name clashes with the java Function type.
So if you want to add Func from 3 upto 16 argument you can do two things.
Example for the second way:
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 for the andThen() and compose() methods. If you add these you must add 16 overloads each: the TriFunc should have an andthen() with 16 arguments. That would give you a compile error because of 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 .NET circular dependencies would be circumvented by using extension methods which are not present in Java.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With