Java 8: Composing functions using compose and andThen
In this post we're going to take a look at function composition using two compose functions provided in the
Function interface -
What is function composition?
It all has to do with creating small reusable functions that you can combine to compose new functions.
Now, how can we achieve this using
Let's first define two simple functions -
Function<Integer, Integer> times2 = e -> e * 2; Function<Integer, Integer> squared = e -> e * e;
Next, let's combine them, using
times2.compose(squared).apply(4); // Returns 32 times2.andThen(squared).apply(4); // Returns 64
As you can see, the difference between
andThen is the order they execute the functions. While the
compose function executes the caller last and the parameter first, the
andThen executes the caller first and the parameter last.
Let's start composing functions
Let's create an example to see how we can use this approach to create small pieces of reusable code - then put them together in different ways.
Consider the following.
We have a list of articles and we need to filter the articles based on different requirements.
Let's start by introducing two basic functions -
byTag - that filter articles based on an author and a tag.
BiFunction<String, List<Article>, List<Article>> byAuthor = (name, articles) -> articles.stream() .filter(a -> a.getAuthor().equals(name)) .collect(Collectors.toList()); BiFunction<String, List<Article>, List<Article>> byTag = (tag, articles) -> articles.stream() .filter(a -> a.getTags().contains(tag)) .collect(Collectors.toList());
Both of these functions are
BiFunctions - meaning they take two parameters.
byAuthor takes the name of an author and the list of articles, returning a list of the articles written by the author requested.
Same goes for
byTag. It takes a tag and the list of articles, returning articles with the requested tag.
BiFunction takes two arguments, it only offers the
andThen function. You can't put the result of a function into a function that takes two arguments, hence the lack of the
Moving on - let's also throw in a basic function that sorts a list of articles from newest to oldest and a function that returns the first article a list.
Function<List<Article>, List<Article>> sortByDate = articles -> articles.stream() .sorted((x, y) -> y.published().compareTo(x.published())) .collect(Collectors.toList()); Function<List<Article>, Optional<Article>> first = a -> a.stream().findFirst();
Now that we have our basic functions, let's see how we can use them to compose new functions.
Let's start by composing a function that will return the article that was most recently published.
Function<List<Article>, Optional<Article>> newest = first.compose(sortByDate);
Using the functions
sortByDate that we created earlier, we're able to create a new function that will return the newest article in a given list.
We can continue to mix these function in several ways to compose functions with new meanings without repeating code.
Finding an author's newest masterpiece.
BiFunction<String, List<Article>, Optional<Article>> newestByAuthor = byAuthor.andThen(newest);
Or just order an author's articles.
BiFunction<String, List<Article>, List<Article>> byAuthorSorted = byAuthor.andThen(sortByDate);
Or maybe you don't care about the author. You just want the newest article based on your favourite tag.
BiFunction<String, List<Article>, Optional<Article>> newestByTag = byTag.andThen(newest);
The point I'm trying to make is that the
Function interface and it's compose functions can make it easier and more intriguing to stay DRY by creating small building blocks that can be combined to fit your needs.
There you go - a few simple ways to compose functions using
andThen. Give it a try as well!