Java.next: Functional coding styles

Functional constructs shared by Groovy, Scala, and Clojure and their benefits

All of the Java.next languages include functional programming constructs, which enable you to think at a higher level of abstraction. However, differences in terminology among the languages can make it difficult to see similar constructs. This installment shows how common functional programming constructs manifest in the Java.next languages, pointing out some subtle differences in the implementation details of those features.

Share:

Neal Ford, Director / Software Architect / Meme Wrangler, ThoughtWorks

Photo of Neal FordNeal Ford is Director, Software Architect, and Meme Wrangler at ThoughtWorks, a global IT consultancy. He is also the designer and developer of applications, instructional materials, magazine articles, courseware, and video/DVD presentations, and he is the author or editor of books spanning a variety of technologies, including the most recent Presentation Patterns. He focuses on designing and building large-scale enterprise applications. He is also an internationally acclaimed speaker at developer conferences worldwide. Check out his website.



24 December 2013

Also available in Chinese Russian Japanese

About this series

The Java™ legacy will be the platform, not the language. More than 200 languages run on the JVM, and it's inevitable that one of them will eventually supplant the Java language as the best way to program the JVM. This series explores three next-generation JVM languages: Groovy, Scala, and Clojure, comparing and contrasting new capabilities and paradigms, to provide Java developers a glimpse into their own near future.

When garbage collection became mainstream, it eliminated entire categories of hard-to-debug problems and enabled runtimes to manage a process that's complex and error-prone for developers. Functional programming aims to do the same for the algorithms you write so you can work at a higher level of abstraction while the runtime performs sophisticated optimizations.

The Java.next languages don't all occupy the same position on the language spectrum between imperative and functional, but all exhibit functional features and idioms. The techniques in functional programming are well-defined, but languages sometimes use different terms for identical functional concepts, making it hard to see the similarities. In this installment, I compare functional coding styles in Scala, Groovy, and Clojure and discuss their benefits.

Imperative processing

I'll start with a common problem and its imperative solution. Suppose you are given a list of names, some of which consist of a single character. You are asked to return the names in a comma-delimited string that contains no single-letter names, with each name capitalized. Java code to implement this algorithm appears in Listing 1.

Listing 1. Imperative processing
public class TheCompanyProcess {
    public String cleanNames(List<String> listOfNames) {
        StringBuilder result = new StringBuilder();
        for(int i = 0; i < listOfNames.size(); i++) {
            if (listOfNames.get(i).length() > 1) {
                result.append(capitalizeString(listOfNames.get(i))).append(",");
            }
        }
        return result.substring(0, result.length() - 1).toString();
    }

    public String capitalizeString(String s) {
        return s.substring(0, 1).toUpperCase() + s.substring(1, s.length());
    }
}

Because you must process the entire list, the easiest way to attack the problem in Listing 1 is within an imperative loop. For each of the names, I check to see if its length is greater than 1, then (if it is greater than 1) append the capitalized name onto the result string along with a trailing comma. The last name in the final string shouldn't include the comma, so I strip it off the final return value.

In imperative programming, you are encouraged to perform operations at a low level. In the cleanNames() method in Listing 1, I perform three tasks: I filter the list to eliminate single characters, transform the list to capitalize each name, then convert the list into a single string. In imperative languages, I'm forced to use the same low-level mechanism (iteration over the list) for all three tasks. Functional languages recognize that filter, transform, and convert are common operations, so they give you a way to attack problems from a different perspective.


Functional processing

Functional programming languages categorize problems differently from imperative languages. The filter, transform, and convert logical categories appear as functions. Those functions implement the low-level transformation and rely on the developer to customize the function's behavior by writing a function that's passed as a parameter. I can conceptualize the problem from Listing 1 in pseudocode as:

listOfEmps -> filter(x.length > 1) -> transform(x.capitalize) -> 
   convert(x, y -> x + "," + y)

With functional languages, you can model this conceptual solution without worrying about the implementation details.

In Scala

Listing 2 implements the Listing 1 processing example in Scala. It reads much like the preceding pseudocode, with necessary implementation details.

Listing 2. Scala processing
val employees = List("neal", "s", "stu", "j", "rich", "bob")
val result = employees
  .filter(_.length() > 1)
  .map(_.capitalize)
  .reduce(_ + "," + _)

Given the list of names, I first filter it, eliminating the names whose length isn't greater than 1. The output of that operation is then fed into the map() function, which executes the supplied code block on each element of the collection, returning the transformed collection. Finally, the output collection from map() flows to the reduce() function, which combines each element based on the rules supplied in the code block. In this case, I combine each pair of elements by concatenating them with an inserted comma. I don't care what the names of the parameters are in any of the three function calls, so I can use the handy Scala shortcut of skipping names with _. The reduce() function starts with the first two elements, concatenating them to produce a single element, which becomes the first element in the next concatenation. As reduce() "walks" down the list, it builds the required comma-delimited string.

I've shown the Scala implementation first because of its somewhat familiar syntax and because Scala uses industry-consistent names — filter, map, and reduce — for the filter, transform, and convert concepts, respectively.

In Groovy

Groovy has the same features but names them more consistently with scripting languages, such as Ruby. The Groovy version of the process example from Listing 1 is in Listing 3.

Listing 3. Groovy processing
class TheCompanyProcess {
  public static String cleanUpNames(List listOfNames) {
    listOfNames
        .findAll {it.length() > 1}
        .collect {it.capitalize()}
        .join(',')
  }
}

Although Listing 3 is structurally similar to the Scala example in Listing 2, the method names differ. Groovy's findAll collection method applies the supplied code block, retaining the elements that the code block is true for. Like Scala, Groovy includes an implicit-parameter mechanism, by means of the predefined it implicit parameter for single-argument code blocks. The collect method — Groovy's version of map— executes the supplied code block on each element of the collection. Groovy provides a function (join()) that concatenates a collection of strings into a single string using the supplied delimiter, which is precisely what I need for this example.

In Clojure

Clojure is a functional language that uses the reduce, map, and filter function names, as shown in Listing 4.

Listing 4. Clojure processing example
(defn process [list-of-emps]
  (reduce str (interpose "," 

      (map clojure.string/capitalize 
          (filter #(< 1 (count %)) list-of-emps)))))

Clojure's thread-first macro

The thread-last macro makes it easy to work on collections. A similar Clojure macro, thread-first, eases interaction with Java APIs. Consider the all-too-common Java code statement person.getInformation().
getAddress().getPostalCode()
, which exhibits the Java tendency to violate the Law of Demeter. This type of statement poses some annoyances in Clojure, forcing developers who consume Java APIs to build inside-out statements, such as (getPostalCode (getAddress (getInformation person))). The thread-first macro unwinds this syntactic annoyance. You can use the macro to write the nested calls as (-> person getInformation getAddress getPostalCode), for as many levels as you like.

Unless you are accustomed to reading Clojure, the structure of the code in Listing 4 might be unclear. Lisps such as Clojure work "inside out," so the place to start is the final parameter value, list-of-emps. Clojure's (filter ) function accepts two parameters: a function (in this case, an anonymous function) to use for filtering and the collection to be filtered. You can write a formal function definition for the first parameter, such as (fn [x] (< 1 (count x))), but with Clojure, you can write anonymous functions more tersely. As in the previous examples, the outcome of the filtering operation is a smaller collection. The (map ) function accepts the transformation function as the first parameter and the collection — which in this case is the return value of the (filter ) operation — as the second. Clojure's (map ) function's first parameter is often a developer-supplied function, but any function that accepts a single parameter will work; the built-in capitalize function matches the requirement. Finally, the result of the (map ) operation becomes the collection parameter for (reduce ). The first parameter for (reduce ) is the combination function ((str ) applied to the return of the (interpose ) function. (interpose ) inserts its first parameter between each element of the collection (except the last).

Even experienced developers suffer when functionality becomes too nested, as it is with the (process ) function in Listing 4. Fortunately, Clojure includes macros that enable you to "unwind" structures into more readable order. The functionality in Listing 5 is identical to that of the Listing 4 version.

Listing 5. Using Clojure's thread-last macro
(defn process2 [list-of-emps]
  (->> list-of-emps
       (filter #(< 1 (count %)))
       (map clojure.string/capitalize)
       (interpose ",")
       (reduce str)))

The Clojure thread-last macro takes the common operation of applying various transformations on collections and reverses the typical Lisp ordering, restoring a more natural left-to-right reading. In Listing 5, the (list-of-emps) collection comes first. Each subsequent form in the block is applied to the preceding one. One of the strengths of Lisp lies in its syntactic flexibility: Anytime code becomes hard to read, you can bend the syntax back toward readability.


Advantages of functional programming

In a famous essay called "Beating the Averages," Paul Graham defines the Blub Paradox: He "invents" an imaginary language named Blub and speculates on the comparison of power between other languages and Blub:

As long as our hypothetical Blub programmer is looking down the power continuum, he knows he's looking down. Languages less powerful than Blub are obviously less powerful, because they're missing some feature he's used to. But when our hypothetical Blub programmer looks in the other direction, up the power continuum, he doesn't realize he's looking up. What he sees are merely weird languages. He probably considers them about equivalent in power to Blub, but with all this other hairy stuff thrown in as well. Blub is good enough for him, because he thinks in Blub.

To many Java developers, the code in Listing 2 looks foreign and odd, so it's hard to view it as advantageous. But when you stop over-specifying the details of how to perform tasks, you free increasingly intelligent languages and runtimes to make powerful improvements. For example, the advent of the JVM — by removing memory management as a developer concern — opened entire realms of R&D for the creation of state-of-the-art garbage collection. With imperative coding, you are mired in the details of how the iterative loop works, which makes optimizations such as parallelism difficult. Thinking about operations at a higher level (such as filter, map, and reduce) decouples concept from implementation, transforming a modification such as parallelism from a complex, detailed undertaking to a simple API change.

Think for a moment about how to make the code in Listing 1 multi-threaded. Because you are intimately involved with the details of what happens during the for loop, you must also handle the nasty concurrency code. Then consider the parallelized Scala version, shown in Listing 6.

Listing 6. Making the process parallel
val parallelResult = employees
  .par
  .filter(f => f.length() > 1)
  .map(f => f.capitalize)
  .reduce(_ + "," + _)

The only difference between Listing 2 and Listing 6 is the addition of the .par method to the stream of commands. The .par method returns a parallel version of the collection that subsequent operations act upon. Because I specify the operations on the collection as higher-order concepts, the underlying runtime is free do more work.

Imperative object-oriented developers tend to think of reuse at the class level because their languages encourage the use of classes as building blocks. Functional programming languages tend toward reuse at the function level. Functional languages build sophisticated generic machinery (such as filter(), map(), and reduce()) and enable customization through functions supplied as parameters. It's common in functional languages to transform data structures into standard collections like lists and maps because then they can be manipulated by the powerful built-in functions. For example, in the Java world, dozens of XML processing frameworks exist, each encapsulating its own private vision of XML structure and delivering it by means of its own methods. In languages such as Clojure, XML is transformed into a standard map-based data structure that is open to powerful transformation, reduction, and filtering operations that are already in the language.


Conclusion

All modern languages either include or are adding functional programming constructs, making functional programming a definite part of your future. The Java.next languages all implement powerful functional features, sometimes with different names and behaviors. In this installment, I illustrated a new coding style in Scala, Groovy, and Clojure and showed some benefits.

In the next installment, I delve into more details of how the languages differ in their filter, map, and reduce implementations.

Resources

Learn

  • Scala: Scala is a modern, functional language on the JVM.
  • Groovy: Groovy is a dynamic variant of the Java language, with updated syntax and capabilities.
  • Clojure: Clojure is a modern, functional Lisp that runs on the JVM.
  • Law of Demeter: Learn about a design guideline for software development that discourages long lists for property access.
  • Beating the Averages (Paul Graham, April 2003): Read about Graham's experiences building ViaWeb, the first online build-it-yourself e-commerce site.
  • Functional thinking: Explore functional programming in Neal Ford's column series on developerWorks.
  • Language designer's notebook: In this developerWorks series, Java Language Architect Brian Goetz explores some of the language design issues that have presented challenges for the evolution of the Java language in Java SE 7, Java SE 8, and beyond.
  • developerWorks Java technology zone: Find hundreds of articles about every aspect of Java programming.

Get products and technologies

  • Download IBM product evaluation versions and get your hands on application development tools and middleware products from DB2®, Lotus®, Rational®, Tivoli®, and WebSphere®.

Discuss

  • Get involved in the developerWorks community. Connect with other developerWorks users as you explore the developer-driven blogs, forums, groups, and wikis.

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Java technology on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology
ArticleID=957258
ArticleTitle=Java.next: Functional coding styles
publish-date=12242013