Primitive Type Specializations of BinaryOperator<T>
Table 13.10 shows that the BinaryOperator<T> interface has three primitive type specializations to int, long, and double. The specializations are named PrimBinaryOperator, where Prim is either an Int, Long, or Double (Table 13.10). These primitive type binary operators have the functional method applyAsPrim: (primitive, primitive) -> primitive, where primitive is an int, long, or double—the operator takes two arguments of a primitive type and returns a result of the same primitive type.
DoubleBinaryOperator areaOfRectangle2 = (length, width) -> length * width;
System.out.printf(“%.2f x %.2f = %.2f%n”,
25.0, 4.0, areaOfRectangle2.applyAsDouble(25.0, 4.0));
// 25.00 x 4.00 = 100.00
13.12 Currying Functions
The functional interfaces in the java.util.function package define functional methods that are either one-arity or two-arity methods. For higher arity functional methods, one recourse is to define functional interfaces whose functional method has the desired arity. The functional interface below defines a three-arity functional method—that is, it takes three arguments.
@FunctionalInterface
interface TriFunction<T, U, V, R> {
R compute(T t, U u, V v); // (T, U ,V) -> R
}
The TriFunction<T, U, V, R> interface can be used to define a lambda expression to calculate the volume of a cuboid.
// (Double, Double, Double) -> Double
TriFunction<Double, Double, Double, Double> cubeVol = (x, y, z) -> x * y * z;
System.out.println(cubeVol.compute(10.0, 20.0, 30.0)); // 6000.0
Another recourse is to apply the technique of currying to transform a multi-argument function into a chain of lower arity functions.
The process of currying is illustrated by implementing the three-arity lambda expression above for calculating the volume of a cuboid. Step 1 below derives a chain of three one-arity functions that will together compute the volume of a cuboid. At (1), parentheses are used explicitly to show the nested lambdas that define each of the one-arity functions—grouping is from right to left. The nesting of the onearity functions is compatible with the nesting of the types in the parameterized functional interface type. An outer function returns its immediate inner function. Step 2 supplies the x argument. This is called partial application, as it returns a function where the remaining arguments y and z are still unknown. Step 3 is also partial application, only the y argument is supplied, returning a function where now only the z argument is unknown. Only at Step 4, when the z argument is supplied at (2), can the final one-arity function be executed. The application of the individual onearity functions can also be chained as shown at (3), without going through the intermediate steps.
// Step 1:
// Partial application: double -> DoubleFunction<DoubleUnaryOperator>
DoubleFunction<DoubleFunction<DoubleUnaryOperator>> uniFuncA
= (x -> (y -> (z -> x * y * z))); // (1)
// Step 2:
// Partial application: double -> DoubleUnaryOperator
DoubleFunction<DoubleUnaryOperator> uniFuncB
= uniFuncA.apply(10.0); // 10.0 * y * z
// Step 3:
// Partial application: double -> double
DoubleUnaryOperator uniOpC = uniFuncB.apply(20.0); // 10.0 * 20.0 * z
// Step 4:
// Application:
double vol1 = uniOpC.applyAsDouble(30.0); // (2) 10.0 * 20.0 * 30.0 = 6000.0
double vol2 = uniFuncA.apply(10.0).apply(20.0).applyAsDouble(30.0); // (3) 6000.0
The sequence in which the arguments are supplied in the currying process is irrelevant, and more than one argument can be supplied in each step in accordance with the target type. Each step is a partial application, except for the last one which executes the final function. The process ensures that the number of unknown arguments decreases in each step.
Here we have provided just a taste of currying, but it is a topic worth exploring further. The technique bears the name of Haskell Curry, a famous mathematician and logician of the twentieth century.