Functional Java

A functional approach to solve Conway’s game of life in Java

David Ibl
5 min readJan 29, 2018

With Java 8 and the introduction of lambda expressions and method handles, the Java programming language got the essential feature to implement algorithms in a functional way.

typical lambda expression example

At the LV 1871 coding dojo, we already coded different implementations of Conway’s game of life using various approaches.

To train lambda expressions and try out the possibilities of Java 8 the exercise should be solved in a functional way.

If we remember the base algorithm we find that the game of life can easily be represented by a list of fields. A game iteration is a transformation algorithm transforming the state of each field. To use Java 8 Streams API to implement this transformation seems to be an obvious thought.

Finally, the gameboard should be represented as a list of immutable field objects containing the coordinates and the life or dead flag.

field model

During past sessions general readability of lambda expressions was a point of interest and the common consense was not to use them too excessive.

One implementation of the game of life ended up with a totally unreadable chain of arrow- and inline-functions. To write the functionality this way and refactor it was not as easy as refactoring conventional code.

usage of arrow function style

Using the streams API with inline delegates is not the right way to keep a program readable and to prevent the duplication of essential code. The Java 8 interfaces that can be found in the java.util.function package can be used with ease to define functional elements as reusable types.

When keeping all the functions small and pure, a toolset created this way can be reused in different scenarios without building complicated and obscure inheritance structures. By delegating domain-specific algorithms into structural code defined as higher-order functions we can even avoid duplicating essential structural functionality.

The first-class goal of functional programming is the abstraction of structural code to reusable units without constructing complicated inheritance structures.

The filter refactored. The equal function reusable.

This example visualizes that lambda expressions can be used as types. They can be used as parameters, return values or variable values. We can reuse them, parameterize them and work with them in nearly every manner. After all, the usage of Java 8 method handles (Type::method) even eases the utilization of functional structures in java programs.

In this way, we are able to abstract structural and recurrent code. A set of tested and well named pure functions can even provide a technical or domain-specific toolset.

It just becomes easy to create some kind of custom DSL with the use of declared expressions.

But how to find out on how to solve a complex algorithm in this way?

In the case of the game of life, we decided to write down the functionality in some kind of humanly readable DSL and implement it later. The result of this approach looks promising.

the game of life game iteration

The main goal, to write expressive readable code, is fulfilled in that way.

It became reality, to express the rules in a human-readable way through functional code.

For sure it is necessary to practice the use of expressions and streams API to write algorithms, which really can be implemented, in that style. But this article will show, how to think to find such a solution.

Let’s start at the beginning. The method toDeadField, should transform a living field to a dead field if some condition is fulfilled. A short version of the rule is: “A field dies if …”.

This means we need a method returning a function which maps a field to a dead field if some kind of condition evaluates in the correct way.

The method finally takes a predicate representing the condition as a parameter. Within the function, the method returns, we now can apply this predicate to test if we have to kill the field. The function itself gets a field as a parameter and returns the original field or a new dead field depending on the result of the given condition.

toDeadField method takes a predicate and returns a function

By the (not obvious) use of the Java 8 optional and it’s filter-method we can even optimize this function in a functional manner. After creation of an optional, we can filter those values not fulfilling the condition and return a new dead field if the condition is fulfilled.

(not obvious) usage of Optional.filter

So let’s take a look inside. ToDeadField requires us to provide a condition deciding whether to kill a field or not. This means which has to return a predicate.

If we read the parameters, we can translate them into their functional representations. The parameters have to be:

  1. A predicate (isAlive)
  2. A function combining two predicates to one predicate (and)
  3. A predicate (which; hasLessThanThree; etc)

Finally, we have to implement a Method returning a predicate and taking two predicates and a BiFunction as its parameters.

which combines two predicates using a given bifunction

The algorithm implemented within “which” is really easy. (lambda experts know that this is just implemented to train. With the “and” method of predicates the implementation of a “which” can be unnecessary)

The first predicate should provide a condition expressing if the given field is alive. The condition can be expressed in many ways. Even many readable ways. In real-world code, we would just use the method handle of the getter. In this case, the usage of the method handle was abstracted to express the condition in a more human-readable way.

A predicate is a function which returns a boolean. The getter of the alive flag returns a boolean, so the getter itself fulfils every requirement to be a predicate.

usage of a getter method handle as a predicate

The function providing a predicate testing if a field is dead, is just the negation of the function testing if a field is alive, after all.

Finally, let’s take a look at the BiFunction combining two predicates. A BiFunction expects two parameters and returns one. The function itself can do whatever required. The given case requires us to combine two predicates into one predicate. In the case of an and, we can just use the predicate’s own “and” method to combine the two predicates.

a generic, pure “and()” function combining two predicates

The rest of the code is just a repetition of the principles learned above. The “which” returns a predicate which can be used as a parameter of a “which”.

With the use of higher order functions expecting functions as parameters and returning functions, we can implement generic DSL units, composable to algorithms in many ways.

Feel free to have a look at the full code repository at GitHub:

--

--

David Ibl

Chief Platform Officer @lv1871 the fanciest assurance company