By Neal Ford

Screen Shot 2014-11-18 at 12.05.54The following is an extract from “Functional Thinking: Functional Programming using Java, Clojure and Scala”, published by OReilly.

 

 

 

 

Why Functional? 

Let’s say for a moment that you are a lumberjack. You have the best axe in the forest, which makes you the most productive lumberjack in the camp. Then one day someone shows up and extols the virtues of a new tree-cutting paradigm, the chainsaw. The sales guy is persuasive, so you buy a chainsaw, but you don’t know how it works. Demonstrating your expertise with the previous tree-cutting paradigm, you swing it vigorously at a tree—without cranking it. You quickly conclude that this newfangled chainsaw is a fad, and you return to your axe. Then, someone appears and shows you how to crank the chainsaw.

The problem with a completely new programming paradigm isn’t learning a new language.  After all, everyone reading this has learned numerous computer languages— language syntax is merely details. The tricky part is learning to think in a different way.

Even if you don’t care about Scala or Clojure, and are happy coding in your current language for the rest of your career, your language will change underneath you, looking more functional all the time. Now is the time to learn functional paradigms, so that you can leverage them when (not if) they appear in your everyday language. Let’s take a look at the reasons why all languages are gradually becoming more functional.

Shifting Paradigms

Computer science often advances in fits and starts, with good ideas appearing decades before they suddenly become part of the mainstream. For example, Simula 67, created in 1967, is the first object-oriented language, yet object orientation didn’t really become mainstream until after the popularization of C++, which first appeared in 1983. Often, good ideas await foundation technologies to catch up. In its early years, Java was regularly considered too slow and expansive in memory usage for high-performance applications, but shifts in the hardware market made it an attractive choice.

Functional programming follows the same conceptual trajectory as object orientation: developed in academia over the last few decades, it has slowly crept into all modern programming languages. Yet just adding new syntax to a language doesn’t inform developers of the best way to leverage this new way of thinking.

I start with a contrast between the traditional programming style (imperative loops) and a more functional way of solving the same problem. For the problem to solve, I dip into a famous event in computer science history, a challenge issued from Jon Bentley, the writer of a regular column in Communications of the ACM called “Programming Pearls,” to Donald Knuth, an early computer science pioneer. The challenge is common to anyone who has written text-manipulation code: read a file of text, determine the most frequently used words, and print out a sorted list of those words along with their frequencies. Just tackling the word-frequency portion, I write a solution in “traditional” Java, shown in Example 1-1.

Example 1-1. Word frequencies in Java

public class Words {
private Set<String> NON_WORDS = new HashSet<String>() {{
2 |
    add("the"); add("and"); add("of"); add("to"); add("a");
    add("i"); add("it"); add("in"); add("or"); add("is");
    add("d"); add("s"); add("as"); add("so"); add("but");
    add("be"); }};
public Map wordFreq(String words) {
TreeMap<String, Integer> wordMap = new TreeMap<String, Integer>(); Matcher m = Pattern.compile("w+").matcher(words);
while (m.find()) {
String word = m.group().toLowerCase(); if (! NON_WORDS.contains(word)) {
if (wordMap.get(word) == null) { wordMap.put(word, 1);
} else {
                wordMap.put(word, wordMap.get(word) + 1);
            }
} }
return wordMap; Chapter 1: Why
} }

In Example 1-1, I create a set of nonwords (articles and other “glue” words), then create the wordFreq() method. In it, I build a Map to hold the key/value pairs, then create a regular expression to allow me to determine words. The bulk of this listing iterates over the found words, ensuring that the actual word is either added the first time to the map or its occurrence is incremented. This style of coding is quite common in languages that encourage you to work through collections (such as regular expression matches) piecemeal.

Consider the updated version that takes advantage of the Stream API and the support for higher-order functions via lambda blocks in Java 8 (all discussed in more detail later), shown in Example 1-2.

Example 1-2. Word frequency in Java 8

private List<String> regexToList(String words, String regex) { List wordList = new ArrayList<>();
Matcher m = Pattern.compile(regex).matcher(words);
while (m.find())
    wordList.add(m.group());
return wordList; }
public Map wordFreq(String words) {
TreeMap<String, Integer> wordMap = new TreeMap<>(); regexToList(words, "w+").stream()
            .map(w -> w.toLowerCase())
            .filter(w -> !NON_WORDS.contains(w))
            .forEach(w -> wordMap.put(w, wordMap.getOrDefault(w, 0) + 1));
return wordMap; }

In Example 1-2, I convert the results of the regular expression match to a stream, which allows me to perform discrete operations: make all the entries lowercase, filter out the nonwords, and count the frequencies of the remaining words. By converting the iterator returned via find() to a stream in the regexToList() method, I can perform the re‐ quired operations one after the other, in the same way that I think about the problem. Although I could do that in the imperative version in Example 1-1 by looping over the collection three times (once to make each word lowercase, once to filter nonwords, and once to count occurrences), I know not to do that because it would be terribly inefficient. By performing all three operations within the iterator block in Example 1-1, I’m trading clarity for performance. Although this is a common trade-off, it’s one I’d rather not make.

In his “Simple Made Easy” keynote at the Strange Loop conference, Rich Hickey, the creator of Clojure, reintroduced an arcane word, complect: to join by weaving or twining together; to interweave. Imperative programming often forces you to complect your tasks so that you can fit them all within a single loop, for efficiency. Functional programming via higher-order functions such as map() and filter() allows you to elevate your level of abstraction, seeing problems with better clarity. I show many examples of functional thinking as a powerful antidote to incidental complecting.

Aligning with Language Trends

If you look at the changes coming to all major languages, they all add functional extensions. Groovy has been adding functional capabilities for a while, including advanced features such as memoization (the ability for the runtime to cache function return values automatically). Even Java itself will finally grow more functional extensions, as lambda blocks (i.e., higher-order functions) finally appear in Java 8, and arguably the most widely used language, JavaScript, has numerous functional features. Even the venerable C++ added lambda blocks in the language’s 2011 standard, and has generally shown more functional programming interest, including intriguing libraries such as the Boost.Phoenix library.

Learning these paradigms now allows you to utilize the features as soon as they appear, either in your use of a new language such as Clojure or in the language you use every day.

Neal Ford on Shifting to Functional Thinking

About The Author
-

1 Comment

  • Marcel Weiher
    Reply

    It looks like the “functional” example is missing the word list, which accounts for 6 of the 18 lines in the “traditional” code. If you take that into account, the difference is 1 line of code ( 11 vs. 12). Not exactly overwhelming.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>