The objective of this blog post is to give you a basic idea of functional composition in Java 8. We will look at 2 methods, andThen and compose which are part of the functional interface, Function. We will then build on it further to show how we can apply a stream to a variable number of functions to combine all the functions.
Let us take a very simple example of 2 functions, one function adds 0.2 to an incoming value and another one adds 0.5 to an input value. So the input is a double and the output is also a double. This can represented as a Function<Double,Double>. Function is the functional interface introduced in Java 8.
Function<Double, Double> func1 = d -> d + 0.2;
Function<Double, Double> func2 = d -> d + 0.5;
Both andThen and compose are default methods.
The andThen method:
To compose these 2 functions together , we can use andThen method from the Function interface. What do we get now ?
func1.andThen(func2)
Let us start with an initial value, 1.0.
Hence :
func1.andThen(func2).apply(1.0)
This gives us an output of 1.7.
How ?
- The call to the andThen method first applies the func1 to 1.0 which is the input. This gives us an output of 1.2 as behavior of func1 is d -> d+0.2, here d = 1.0
- The output from above , 1.2 , serves as an input to func2 and hence we get 1.2+0.5 = 1.7.
The compose method:
To understand this let us slightly modify the 2 functions:
Function<Double, Double> func1 = d -> d + 0.2;
Function<Double, Double> func2 = d -> d * 10.0;
The second function multiplies the incoming value by 10.
If we were to do:
func1.compose(func2).apply(1.0)
The output is 10.2
How?
- The call to the compose method first applies the func2 to 1.0 which is the input. This gives us an output of 10.0 as behavior of func2 is d -> d * 10.0, here d = 1.0
- The output from above , 10.0 , serves as an input to func1 and hence we get 10.0 + 0.2 = 10.2
What if we applied andThen to the above? Well, the output is 12.0 . Surprised ?
Why?
- The call to the andThen method first applies the func1 to 1.0 which is the input. This gives us an output of 1.2 as behavior of func1 is d -> d+0.2, here d = 1.0
- The output from above , 1.2 , serves as an input to func2 and hence we get 1.2 * 10.0 = 12.0.
Remember:
The andThen method applies the first function, func1 to the input, that is the parameter to the apply method but the compose method operates by applying func2 to the value in the apply and then passes the value to func1.
andThen:
func1.andThen(func2).apply(1.0)
compose:
func1.compose(func2).apply(1.0)
More fun with functional composition:
Let us go back to :
Function<Double, Double> func1 = d -> d + 0.2;
Function<Double, Double> func2 = d -> d + 0.5;
When we did the following, we got the output as 1.7 :
func1.andThen(func2).apply(1.0)
Let us apply a Stream.of to func1 and func2.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Function<Double,Double> combined = Stream.of(func1,func2) | |
.reduce(Function.identity(), Function::andThen); | |
combined.apply(1.0); |
The output from this is also 1.7
The Stream.of(…) returns a stream of Function<Double,Double>. The reduce method takes a Function.identity(), this is just saying, hey whatever is the next element in the stream, return it to me as it is. The Function::andThen specifies that combine each element in the stream with andThen(). So <element_from_the stream>.andThen<next_element_from_stream>. The combination of stream and reduce method combines or chains all the functions together and gives us a single Function<Double,Double> which is a chain of functions.
Now how is this useful ? We can create a function which accepts a variable number of functions and then they can all be chained in one place.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.util.function.Function; | |
import java.util.stream.Stream; | |
public class FunctionalChaining { | |
public static void main(String[] args) | |
{ | |
Function<Double, Double> func1 = d -> d + 0.2; | |
Function<Double, Double> func2 = d -> d + 0.5; | |
// Simple chaining | |
System.out.println(func1.andThen(func2).apply(1.0)); | |
// Chaining using Stream.of | |
Function<Double, Double> combined = Stream.of(func1, func2) | |
.reduce(Function.identity(), Function::andThen); | |
System.out.println(combined.apply(1.0)); | |
// Pass it to a function which can accept variable number of functions. | |
System.out.println(combineFunctions(func1, func2)); | |
} | |
private static Double combineFunctions(Function<Double, Double>… combineFunctions) | |
{ | |
Function<Double, Double> chainedFunction = Stream.of(combineFunctions) | |
.reduce(function -> function,Function::andThen); | |
return chainedFunction.apply(1.0); | |
} | |
} |
Output from all 3 types:
1.7
1.7
1.7
We used a very simple Function<Double,Double> which just added a double value but we could easily pass and chain functions which do a lot more.Function composition is a powerful mechanism which can be used to solve a variety of problems.Think about the decorator pattern or how consecutive filters are applied , can’t we use functional composition instead ?