CHAPTER 8
Programs must be written for people to read, and only incidentally for machines to execute.1
➤ Hal Abelson and Jerry Sussman
Composing with Lambda Expressions
With Java 8 we have two powerful tools: the object-oriented approach and the functional style. They are not mutually exclusive; they can work together for the greater good.
In OOP we often mutate state. If we combine OOP with the functional style, we can instead transform objects by passing lightweight objects through a series of cohesive functions. This can help us create code that’s easier to extend—to produce a different result we simply alter the way the functions are composed. We can use the functions, in addition to the objects, as com- ponents to program with.
In this chapter we look into function composition. Then we use that to create a practical working example of the popular MapReduce pattern, where we scatter independent calculations, and gather the results to create the solution.
As a final step, we parallelize those calculations almost effortlessly, thanks to the ubiquitous JDK library.
pocket and some smaller bills to appear. Mixing OOP and functional style is like that; we send lightweight objects to functions and expect other objects to emerge.
In this combined approach, to achieve a task we chain a series of appropriate functions. As objects pass through the functions in the series, they transform into new objects to produce the desired result. We can see the difference between a pure OOP and a mixed OOP-functional style in the following figure.
In pure OOP, at least the way it’s used in Java, over time an object’s state goes through transitions. In the combined approach, we see lightweight objects transform into other objects rather than transition state.
Object State
Time
Object's state is mutated and goes through state transition
F1
O1 O2 F2 O3 F3 O4
Figure 10—Pure OOP vs. hybrid OOP-functional style
Let’s work with an example to get a better feel for this. We’ll start with a list of ticker symbols and, from it, create a sorted list, with each item correspond- ing stock valued over $100. In the habitual approach we’d walk through the list using an external iterator and update a mutable collection. Instead we’ll transform objects. We’ll filter the tickers list into a list of tickers priced over
$100, then sort the list, and finally report.
We need a sample list of ticker symbols, so let’s start with that.
applying/fpij/Tickers.java public class Tickers {
public static final List<String> symbols = Arrays.asList(
"AMD", "HPQ", "IBM", "TXN", "VMW", "XRX", "AAPL", "ADBE",
"AMZN", "CRAY", "CSCO", "SNE", "GOOG", "INTC", "INTU",
"MSFT", "ORCL", "TIBX", "VRSN", "YHOO");
}
We have some twenty symbols in this sample list. We need to determine the price for each stock. We saw the code to fetch the latest price from Yahoo! in Integrating with the Web Service, on page 71; we can reuse that here. Let’s revisit that code to refresh our memory.
designing/fpij/YahooFinance.java public class YahooFinance {
public static BigDecimal getPrice(final String ticker) { try {
final URL url =
new URL("http://ichart.finance.yahoo.com/table.csv?s=" + ticker);
final BufferedReader reader =
new BufferedReader(new InputStreamReader(url.openStream()));
final String data = reader.lines().skip(1).findFirst().get();
final String[] dataItems = data.split(",");
return new BigDecimal(dataItems[dataItems.length - 1]);
} catch(Exception ex) {
throw new RuntimeException(ex);
} } }
The getPrice() method will return the latest price for a given stock. Since we’re looking for only stocks valued over $100, we can use Stream’s filter() method to trim down the list. Once we get the short list, we can sort it easily using Stream’s sorted() method. Finally we can concatenate the symbols to print. These are all operations we’ve seen before, coming together here to help with this task. Let’s look at the code.
applying/fpij/Stocks100.java
final BigDecimal HUNDRED = new BigDecimal("100");
System.out.println("Stocks priced over $100 are " + Tickers.symbols
.stream() .filter(
symbol -> YahooFinance.getPrice(symbol).compareTo(HUNDRED) > 0) .sorted()
.collect(joining(", ")));
The series of operations flows nicely in a chain. The operations are associative;
the stream of ticker symbols is filtered, sorted, and concatenated. As we move through the composed functions, the original list of symbols is left unmodified, but we transform from that into a filtered stream of symbols, then into a stream of sorted symbols. We finally join the symbols in this last stream for printing. If instead of sorting we want to pick a particular symbol, let’s say the first, we only have to slightly alter the chain; we can reuse most of the Using Function Composition
•
137functions. Let’s visit the stock market to see which stocks in the list are valued at over $100.
Stocks priced over $100 are AAPL, AMZN, GOOG, IBM
The ability to compose functions into a chain of operations is powerful and has quite a few benefits. It makes the code easier to understand. The lack of mutability reduces the chance of errors and makes it easier to parallelize the code. We can alter a few links in the chain and easily alter the behavior along the way. We’ll see these benefits come to life in the next examples.