Is Java 8 a Functional Language? Three ways it is – Three ways it ain’t

April 15th, 2015 @   - 

This article appeared in Software Developer’s Journal (May 2014).

Synopsis
There’s been lots of excitement surrounding the release of Java 8 and its new functional programming paradigm. After all, it is the greatest shift for Java in its 18-year history. But lost in the noise is the backstory of functional programming. What is functional programming? How does it fit into Java? And can Java now to be considered to be a fully functional language? In this article, I explore these questions and show how paradoxically Java is and isn’t a functional language.

Introduction
It took a long time to release Java 8 and with good reason. It has been reworked, retooled, and rebranded under the guise of a functional programming language. But Java and functional programming come from very different places. Until now, Java has been a predominately imperative and object-oriented language and developers have been predisposed to think in those terms. Like human languages, programming languages shape our thinking.

Object-oriented languages confer first-class citizenship to classes. Classes encourage behaviour and state to be bundled together; this is the essence of encapsulation. Object-orientation is good at making large-code bases manageable through code reuse via inheritance and composition. In this regard, object-orientation is primarily concerned about code structure. Java’s other distinctive trait is that it is an imperative language, which forces developers to express algorithms in a step-by-step fashion. Together, these paradigms have greatly influenced the Java language, its standard and 3rd-party libraries and the eight million Java developers who make up the Java community.

On the other hand, functional programming is a much different paradigm that has until now been a foreign concept to Java. While Java can trace its ancestry to C++, Simula, Smalltalk, Eiffel, and Algol amongst others, functional programming traces its ancestry from lambda calculus, which has spawned languages such as like LISP, Common Lisp, Scheme, ISLISP, and Clojure.

What exactly is functional programming? It’s a programming paradigm whose core principles include:
• The treatment of functions as first-class citizens of the language
• The avoidance of residual state and the emphasis on immutability
• The avoidance of side effects

Functional programming confers first-class status to functions and coerces developers into thinking in those terms. It is primarily concerned about how functions can be re-assembled to create new functions. Pushing this thinking a little further creates a paradigm whereby behavior can be expressed declaratively. Declarative programming allows developers to code in a way that instructs what we want done not how we want it done.

The avoidance of side-effects and emphasis on immutability are sound design philosophies because we can better reason about code once those dynamic aspects are minimized or removed from programs. Furthermore, these design philosophies are particularly amenable to modern multi-core CPU architectures. If we can remove side-effects and state, we can design parallel processing whereby data sets can be divided and processing distributed to individual cores then re-assembled to form a whole. This is in sharp contrast to Java’s traditional parallel processing model whose threads time-share the CPU and access to shared state is managed by locks; a far more difficult proposition to get it right even in the most capable hands.

Ultimately, functional programming tends to focus on behavior rather than structure and naturally, this lead to programs designed differently.

Clashing perspectives
Java and functional programming have different and sometimes even clashing worldviews. With such divergence, can Java really be reinvented to be functional? The short answer is: yes and no. Retrofitting functional concepts into the language has been quite an extraordinary feat of software engineering. Java is a mature successful language with a huge industry footprint. It continues to be a top-tier programming language eighteen years after its introduction. Suddenly making a language functional under these circumstances requires not only a language change but a community buy-in as well. People need to understand the new syntax, libraries and functional mindset. With these considerations in mind, let’s see how Java is and is not functional.

Java 8 is Functional Language because of…

1) Lambdas
Lambdas are the gateway to functional programming and their addition to the language is a must for Java to be considered functional. Java 8 introduces the new “->” syntax to define lambdas. A very simple example is:

(x, y) -> x * y

Here, we create a lambda that expects a value for parameters x and y, which can be an integer, float or double, and simply multiplies them together. These single-statement lambdas are called lambda expressions and enjoy special syntactical privileges including the omittance of curly-braces, semi-colons, and return statements. The ease with which lambdas can be defined on the fly makes them easy to pass around in your code. They embody the concept of behavior-as-data where behavior is passed around to methods. Lambdas are backed by functional interfaces, a new Java 8 concept, which are simply interfaces with exactly one abstract method. Java 8 ships with 40-something standard interfaces that can be used to create any lambda. In the example below, we use the BiFunction functional interface, which declares a method named apply() expecting two parameters and returning one.

package java.util.function;
@FunctionalInterface
public interface BiFunction<T, U, R> { 

R apply(T t, U u);

}

What happens to those two parameters is decided by the lambdas using it. In this example, we pass a lambda to computeShapeArea(), which defines the formula to calculate the area of a shape.

public static double computeShapeArea (double measure1, double measure2, BiFunction<Double, Double, Double> function)
{

return function.apply(measure1, measure2);

}

computeShapeArea(10, 5, (w, l) -> w * l); // Rectangle
computeShapeArea(10, 5, (h, b) -> h * b / 2); // Triangle
computeShapeArea(10, 0, (r, p) -> Math.PI * Math.pow(r, 2)); // Circle

Lambdas are behavior-only constructs and cannot hold state. Their syntax allows code to be expressed concisely. They can be used to implement advanced functional concepts. These qualities are very much in tune with the functional philosophies and with lambdas, Java is certainly a functional language.

2) Functionalized collections
The Collections library has been revamped to better accommodate functional thinking. However, this is a legacy library and was designed in an object-oriented way whereby a conversation is held between the Collections object and the caller: the Collections object is created, data is stored, data is pulled out, processing is done externally, and data is stored back in. Now, many new methods have been added throughout the library to instead pass behavior and let the collection do the processing internally.

The most basic example is forEach().

// Defined in the Iterable interface and extended in Collection
default void forEach(Consumer<? super T> action)

This method is defined high in the Collections hierarchy and so is available to Lists and Sets and many others. It works in conjunction with a Consumer functional interface and allows a lambda to perform any orthogonal processing without returning any data to the caller.

package java.util.function;
@FunctionalInterface
public interface Consumer {

void accept(T t);

}

It can be used this way:

Collection stooges = Arrays.asList(“Larry”, “Moe”, “Curly”);
// Print the contents of the stooges collection
stooges.forEach(s -> System.out.println(s);

Here, we create an ArrayList and simply iterate over its contents and print the elements.
In this second example, we supply a lambda to the method replaceAll() that defines the behavior to feminize the names of the three stooges:

// Create the lambda to feminize the names
UnaryOperator feminize =
s -> “Larry”.equals(s) ? “Lara” : “Moe”.equals(s) ? “Maude” : “Shirley”;

// Replace all male names with their female counterparts
theThreeStooges.replaceAll(feminize);

ReplaceAll() uses a UnaryOperator as its functional interface, which is a Function sub interface and takes a parameter (in this case String) and returns another parameter of the same type.
The Collections library has too many other changes to enumerate in this article but the central theme is that lambdas are used to define behavior and processing is done internally within the Collections object. This thinking is very much aligned with functional thinking and so new Collections library makes Java a functional language.

3) Streams
Streams are the most exciting feature of Java 8 and is where Java realizes its functional self. Streams allow entire algorithms to be expressed declaratively, concisely, and using a fluent interface style. For example, we can express an algorithm to determine whether or not a number is perfect. (A number is mathematically perfect if its divisors when summed are equal to itself.)

private static boolean isPerfect(long n) {

return n > 0 && LongStream.rangeClosed(1, n / 2).filter(i -> n % i == 0).reduce(0, (l, r) -> l + r) == n;

}

Being declarative constructs, we can just as easily parallelize this algorithm to determine a number’s perfectness like this:

private static boolean isPerfect(long n) {

return n > 0 && LongStream.rangeClosed(1, n / 2).parallel().filter(i -> n % i == 0).reduce(0, (l, r) -> l + r) == n;

}

Of course there are caveats when parallelizing streams but this example clearly shows the power of a declarative style of programming.

The streams API defines six families of operations that can be categorized as build, filter, map, reduce, iterate and peek. They encourage a conveyor belt mindset by which each operation’s output, is the input to next operation (also known as a fluent interface style). Streams are mostly lazy in that processing only occurs if the data makes it onto the next operation. Streams do not hold conversational state. That is once the execution complete, the stream is no longer useful and you don’t invoke any of its methods. Also, for best results, you need to think in functional terms. You don’t mutate class attribute state, you don’t write lambdas that have side-effects, and you don’t interfere with the underlying stream elements. Streams force you to think functionally and with streams, Java 8 is at its functional best.

In short, lambdas, the functionalized Collections library and streams are the foundation of the new functionalized Java. But that enough to make it a functional programming language? Let’s explore the flip side.

Java 8 is not Functional Language Because of …

1) Functional programming as a module
The language syntax has been minimally changed to accommodate functional programming and limited to only three new symbols:
• -> (lambdas)
• :: (method references)
• Default (default methods)

Thus its newly found functional flavor has been baked into the new functional libraries rather than in the syntax. Contrast this with a language that allows list comprehensions, folds and currying right at the syntax level. Having functional programming realized at the library instead modularizes the paradigm. You can easily opt-out of functional programming if you wish.

This is a double edged sword because expressing functional code in libraries is treating functional programming as a second-class citizen. As a non-native concept, functional programming feels less natural, harder to reason with, and more verbose. This is exacerbated by the fact that most of the standard functional interfaces are implemented with generics to make them more widely applicable but also harder to comprehend (as generic generally do).

Consider how currying looks like in Java code. Currying is a functional programming technique of transforming a function that takes multiple arguments in such a fashion that it can be called multiple times each with single-argument invocations. In this curried function, we add 20 to 40.

Function<Integer, Function<Integer, Integer>>
curriedAdd = a -> b -> a + b;
Function<Integer, Integer> curriedAdd2 = curriedAdd.apply(20);
Integer result = curriedAdd2.apply(40);

Currying works well in Java but feels a little less natural than say a language like Groovy, which supports currying in its native syntax:

def addition = {x, y -> return x + y}
def curriedAddition = addition.curry(20)
def result = curriedAddition(40)

2) Java’s DNA is still object-oriented
One of the greatest strengths of Java is its rich eco-system of standard, open-sourced and commercial libraries. These have been designed with object-orientation in mind. Object orientation shapes our thinking along the lines of this:
• Create an object
• Interact with it, change its state, fetch its state, keep a conversation going
• Destroy the instance

Of course, this goes against the grain of functional programming so when we try to use these classes in a functional way, our code is slightly less elegant than it would be in a more naturally functional language. Take this quick sort example implemented with streams. The idea of quick sort is:
1. Find the midpoint of a given array (the pivot).
2. Determine all values that are less than the pivot on the left side and all those greater on the right side and swap the lower with the higher until the comparison reaches the pivot.
3. Split the array at the pivot and repeat the process recursively for both halves.
4. Repeat the entire process until each sub-array can no longer be split.

In functional Java, quick sort looks like this:
public static List quickSort(List array) {
   List returnArray = array;

   if (array.size() > 1) {

      // Step 1
      int mid = array.get(array.size() / 2);

      // Step 2
      Map<Integer, List> map = array.stream().
      collect(Collectors.groupingBy(i -> i < mid ? 0 : i == mid ? 1 : 2));

      // Step 3 & 4
      List left = functionalSort(map.getOrDefault(0, new ArrayList<>()));
      List middle = map.getOrDefault(1, new ArrayList<>());
      List right = functionalSort(map.getOrDefault(2, new ArrayList<>()));

      left.addAll(middle);
      left.addAll(right);

      returnArray = left;

}

 return returnArray;

}

Now consider a Scala version of Quicksort (from Martin Odersky’s Scala by Example)

def sort(xs: Array[Int]): Array[Int] = {

if (xs.length <= 1) xs else { val pivot = xs(xs.length / 2) Array.concat( sort(xs filter (pivot >)),

      xs filter (pivot ==),

     sort(xs filter (pivot <)))

}

}

If we look at the cause, verbosity stems from the fact that each bucket’s sub-array is stored inside a list. Then, that list is merged with the other two buckets. It takes a total of six lines of code to implement step 3. Part of the problem is due to Java’s object-oriented DNA. Lists are meant to be created and interacted with as a series of calls within a conversation. This is a hallmark of object-oriented programming. Using the fluent interface style, as adopted by streams, leads to code that is more concise and more aligned to functional programming. Here, however, the two styles are at odds. The addAll() statement prevents us from chaining multiple statements as a pipeline because it does not return the merged list itself but rather a Boolean to indicate the results of the merge. This was a deliberate design choice of Java’s syntax and we often see this in the standard libraries. The end result is that the fluent interface style, which made the Scala example a little more elegant, is inhibited.

3. Non industrial-grade recursion
Recursion is an important tool when used to abstract advanced mathematical concepts. Industrial-grade support is the idea that a method can be called recursively as many times as the necessary without program crashes. Of course, it is impossible to use endless method recursion because every iteration consumes memory in the form of stack frames to store local attributes. However, some functional languages have tail-call optimizations which bypass the stack when the last statement is a call to itself. Think about it: why store the state of the local attribute if it is certain that it will not be used once it unstacks?

Java is not a tail-call optimized language and so it lacks industrial-grade recursion support. Executing this code snippet will create a stack overflow for n > 12000 (can vary depending on your JVM configuration):

private static long factorial(long n, long accumulate) {

return n == 1 ? 1 : factorial(n – 1, n * accumulate);

}

Yet, in byte code, the very last operation is recursive invocation of factorial. In a tail-call optimized language, this could be used to compute very large numbers. Unfortunately, this is not the case for Java. If you really need industrial-grade recursion support, you can use software workarounds such as trampolining.

Conclusion
When all aspects are considered, can it be said that Java is on a level playing field with newer languages built functionally from the ground up? My take on it is that Java is now a hybrid mix of imperative programming and objected-oriented programming sprinkled with functional flavorings. It can be used functionally but due to its legacy, it cannot do everything that a strongly functional language can do. Sometimes, it just won’t be as elegant. This is neither good nor bad. We use different tools for different things and we don’t define functional programming to be good and everything else to be bad.

However, Java now offers a brand new set of tools to solve some problems in a better way. The language designers have managed this without destroying its essence that made it a successful language in the first place. In that regard, I think Java 8 is a success. Java will remain a top-tier language for a long time to come.

Tags:

Comments are closed.