Learn with our tutorials and training

developerWorks provides tutorials, articles and other
technical resources to help you grow your development skills
on a wide variety of topics and products. Learn about a specific
product or take a course and get certified. So, what do you want to learn
about?

Featured products

Featured destinations

Find a community and connect

Learn from the experts and share with other developers in one of our
dev centers. Ask questions and get answers with dW answers. Search for local events
in your area. All in developerWorks communities.

Content series:

This content is part # of # in the series: Functional thinking

This content is part of the series:Functional thinking

Stay tuned for additional content in this series.

About this series

This series aims to reorient your perspective toward a functional mindset, helping you look at common problems in new ways and find ways to improve your day-to-day coding. It explores functional programming concepts, frameworks that allow functional programming within the Java language, functional programming languages that run on the JVM, and some future-leaning directions of language design. The series is geared toward developers who know Java and how its abstractions work but have little or no experience using a functional language.

Functional programming has its roots in both mathematics and computer science, both of which have strong opinions about terminology. Language and frameworks designers develop their favorite nomenclature, only to discover that the underlying paradigms already have names. Learning functional programming paradigms is difficult because of the inconsistency of terminology.

In "Tons of transformations," I took the problem of classifying prime numbers and implemented a solution across several functional languages on the JVM and two functional Java frameworks. Continuing that theme, this installment optimizes the previous algorithm in a couple of ways, showing the subsequent changes across languages. This installment, like the last, illustrates the differences in terminology and availability of features across tools and languages. In particular, I exercise map, filter, and memoize for these examples.

Prime-number classification optimized, in pure Java

The stated problem is determining whether a number is a prime number, one whose only factors are 1 and itself. Among the several algorithms that solve this problem, I've chosen to illustrate filtering and mapping in the functional programming world.

In the last installment, I took a naive approach to the algorithm to determine a number's factors, opting for simple code rather than optimally performing code.
In this installment, I optimize that algorithm using several different functional concepts. In addition, I optimize each version for the use case in which the class is invoked multiple times to classify the same number.

In Listing 1, the getFactors() method iterates the potential factors from 2 to the number being classified, which is quite inefficient. Consider the fact that factors always occur in pairs; that suggests that when I find one factor, I can ascertain its mate by simple division. Thus, I don't need to iterate all the way to the number; I can instead iterate up to the number's square root, harvesting the factors in pairs. The improved getFactors() method appears within the overall-improved version in Listing 2:

In the getFactors() method in Listing 2, I iterate from 2 to the square root of the number (plus 1, to handle rounding errors) and harvest the factors in pairs. Returning a Set is important in this code because of an edge case concerning perfect-square numbers. Consider the number 16, whose square root is 4. In the getFactors() method, using a List instead of a Set will generate duplicate 4s in the list. Unit tests exist to find edge cases like this!

The other optimization in Listing 2 concerns multiple invocations. If the typical usage of this code is to evaluate the same number many times for primeness, the calculation that the sumFactors() method in Listing 1 performs is inefficient. Instead, in the sumFactors() method in Listing 2, I create a class-wide cache to hold previously calculated values.

Achieving the second optimization requires a little dubious class design, forcing it to be stateful so that the instance can act as the owner of the cache. This could be improved, but the improvement is trivial in subsequent examples, so I won't bother here.

Optimized Functional Java

Functional Java (see Related topics) is a framework to add functional features to Java. The two methods that optimization affects are the getFactors() and sumFactors() methods, whose original (unoptimized) versions appear in Listing 3:

The getFactors() method in Listing 3 filters the range of numbers from 1 to the target number plus 1 (because ranges in Functional Java are noninclusive) using the isFilter() method to determine inclusion. The optimized version of the Functional Java prime-number classifier appears in Listing 4:

In the getFactors() method in Listing 4, I use the same range() and filter() methods more selectively. The first range harvests the factors up to the square root, using the filter() method as in Listing 3. The second line of the method uses the map() method from Functional Java to generate the factors above the square root. The map() method applies the function to each element in the collection, returning a transformed collection. This list of factors above the square root is appended to the factors below the square root (lowerFactors). The last method call to Functional Java's nub() method converts the list to a set, alleviating the perfect-square duplication problem.

The sumFactors() optimization in Listing 4 uses a cache identically to the pure Java version in Listing 2, implying the same statefulness requirement on the class as in that version.

Optimized Groovy

The original Groovy versions of the getFactors() and sumFactors() methods appear in Listing 5:

Listing 5. Original Groovy getFactors() and sumFactors() methods

In Groovy, the findAll() method filters the range of numbers, and the sumFactors() method uses Groovy's inject() method, applying the code block to each element to reduce the list to one element (which will be the sum, because the code block sums each pair as the reduction operation). The optimized Groovy version of the prime number classifier appears in Listing 6:

Just as in the Functional Java version, the factors() method in Listing 6 partitions the factors using the square root and converts the resulting list into a set via the toSet() method. The primary difference is the terminology difference between Functional Java and Groovy; in Functional Java, the filter() and foldLeft() methods are synonyms for Groovy's findAll() and inject(), respectively.

The optimization solution in Listing 6 is radically different from the previous Java versions. Rather than add statefulness to the class, I use Groovy's memoize() method. The factors method in Listing 6 is a pure function, meaning that it relies on no state except its parameter. Once that requirement is met, the Groovy runtime can cache the values automatically via the memoize() method, which returns a cached version of the factors() method named getFactors(). This is a great example of the ability of functional programming to reduce the number of mechanisms the developer must maintain, such as caching. I cover memoization more fully in the "Functional design patterns, Part 1" installment of this series.

Optimized Scala

The original Scala versions of the getFactors() and sumFactors() methods appear in Listing 7:

The optimized factors() method uses the same technique as previous examples (such as in Listing 3), adapted to Scala's syntax, yielding a straightforward implementation.

Scala does not have a standard memoization facility, although it is suggested as a future addition. It can be implemented numerous ways; an easy implementation relies on Scala's built-in mutable map and its handy getOrElseUpdate() method.

Optimized Clojure

The Clojure versions of the factors and sum-factors methods appear in Listing 9:

Listing 9. Original factors and sum-factors methods

As in the other preoptimized versions, the original Clojure code filters the range of numbers from 1 to the number plus 1, and it uses Clojure's reduce function to apply the + function to each element, yielding the sum. The optimized Clojure prime-number classifier appears in Listing 10:

The factors method uses the same optimized algorithm as used in
previous examples (such as Listing 3), harvesting the factors below the square root by filtering the range from 1 to the square root plus 1: (filter #(factor? n %) (range 1 (inc (Math/sqrt n)))). The Clojure version uses its own symbol (%) for unnamed parameters, like the Scala version in Listing 8. The #(/ n %) syntax creates an anonymous function, as syntactic-sugar shorthand for (fn [x] (/ n x)).

Clojure includes the ability to memoize pure functions via the memoize function, just as in the Groovy version, making the second optimization trivial to implement.

Conclusion

In this installment as in the last, I illustrated how similar concepts have grown different names across languages and frameworks, as well as built-in abilities. Groovy is the odd language out when it comes to function naming (findAll() rather than the more common filter(), and collect() rather than map(), for example). The presence of memoization makes a huge difference for the ease and safety of implementing caching.

In the next installment, I'll explore laziness more fully across languages and functional frameworks.