Big Data/Analytics Zone is brought to you in partnership with:

Mario has posted 14 posts at DZone. You can read more from them at their website. View Full User Profile

A Refactoring Exercise Using Java8 Lambda Expressions

03.14.2013
| 10897 views |
  • submit to reddit

Finally Java with its 8th major release will get lambda expressions and then will become a bit more functional. Together with Raoul-Gabriel Urma and Alan Mycroft I started writing a book on this topic. Anyway just using lambda expression is probably not enough to claim that you are doing functional programming and, more important, to leverage its potentialities. Now Java supports a new programming paradigm and then people who, like me, did imperative object oriented programming in Java for a dozen years are required to do a quite big mind shift to effectively use these new techniques. For instance, in a former article, I suggested that it's time to stop using the null reference, or at least start using it more sparingly, and replace it with a safer and more explicit construct like an Option.  

In my opinion, a good way to learn how to put lambda expressions at work in a more idiomatic way is to read articles written using other functional languages, better if Scala since it is the most similar to Java, and try to reimplement in Java 8 the patterns and solutions suggested there. This is what I did with the present post that is just a Java translation of a Scala article written by Debasish Ghosh.

The author of that article tried to solve the following, apparently trivial, problem: calculate the net salary of an employee starting from his basic one and applying in sequence the following 4 methods that adds bonuses and subtract taxes to it.

public class SalaryCalculator {
  // B = basic + 20%
  public double plusAllowance(double d) {
  return d * 1.2;
  }

  // C = B + 10%
  public double plusBonus(double d) {
  return d * 1.1;
  }

  // D = C - 30%
  public double plusTax(double d) {
  return d * 0.7;
  }

  // E = D - 10%
  public double plusSurcharge(double d) {
  return d * 0.9;
  }
}

To make the problem a bit more interesting, Debasish also supposed that the application of all those 4 bonuses or taxes was, one by one, optional. With this last condition a simple, but imperative way, to calculate the net salary could be the following:

public double calculate(double basic, boolean... bs) {
  double salary = basic;
  if (bs[0]) salary = plusAllowance(salary);
  if (bs[1]) salary = plusBonus(salary);
  if (bs[2]) salary = plusTax(salary);
  if (bs[3]) salary = plusSurcharge(salary);
  return salary;
}

Here bs is an array of 4 booleans (for conciseness I omitted the check on the array length) where each boolean states if the corresponding method should be applied or not. So for example:

calculate(1000.0, true, false, false, true);

calculates the net salary starting from a basic one of 1000 and applying only the allowance (1st method) and the surcharge (4th). So far so good, but as I said, the provided solution is too imperative to satisfy our new functional tastes. As first thing, in order to rewrite it in a more functional style, possibly getting rid of the mutable variable salary and the 4 conditional branches, let's notice that all the 4 methods used to calculate the net salary are in reality a special type of function, called endomorphism, where the only input parameter and the returned value are of the same type. In Java terms an endomorphism can be simply defined as:

interface Endomorphism<A> extends Function<A, A> { }

Actually in the standard Java 8 API it already exists an interface called UnaryOperator defined exactly in the same way. I renamed it Endomorphism here because I think this name is more correct. However, if you prefer, you can just replace my Endomorphism interface with the standard UnaryOperator one in the rest of the article and everything else will work exactly in the same way. To pursue our goal we will also need a second abstraction, a Monoid:

interface Monoid<A> {
  A append(A a1, A a2);
  A zero();
}

In category theory a monoid is defined as a semigroup with identity. On planet Earth it is just an associative operation on a type that also has a zero. It is now possible to combine these two abstractions and define a Monoid on the Endomorphism type as it follows:

interface EndoMonoid<A> extends Monoid<Endomorphism<A>> {
  @Override
  default Endomorphism<A> append(Endomorphism<A> a1, Endomorphism<A> a2) {
  return (A a) -> a2.apply(a1.apply(a));
  }

  @Override
  default Endomorphism<A> zero() {
  return a -> a;
  }
}

Here the associative operation is the composition of the two functions (I didn't reuse directly the compose() method defined on the Function interface that Endomorphism extends only because it returns a Function instead of an Endomorphism) and the zero is simply the identity function. This is in fact the zero of our Monoid because the composition of any Endomorphism with it returns the Endomorphism itself. Note that I defined another interface instead of class because, using the keyword default, Java 8 allows to add behavior, i.e. implemented methods, to an interface provided that they have no state. In the end we can implement this last interface with an actual class which only purpose is to encapsulate an Endomorphism and provide some convenient methods that will allow us to calculate the net salary with a fluent, and of course functional style.

public class FluentEndoMonoid<A> implements EndoMonoid<A> {
  private final Endomorphism<A> endo;

  public FluentEndoMonoid(Endomorphism<A> endo) {
  this.endo = endo;
  }
   
  public FluentEndoMonoid(Endomorphism<A> endo, boolean b) {
  this.endo = b ? endo : zero();
  }

  public FluentEndoMonoid<A> add(Endomorphism<A> other) {
  return new FluentEndoMonoid<A>(append(endo, other));
  }
   
  public FluentEndoMonoid<A> add(Endomorphism<A> other, boolean b) {
  return add(b ? other : zero());
  }

  public Endomorphism<A> get() {
  return endo;
  }

  public static <A> FluentEndoMonoid<A> endo(Endomorphism<A> f, boolean b) {
  return new FluentEndoMonoid<A>(f, b);
  }
}

In particular the add() method accepting both an Endomorphism and a boolean combines the current Endomorphism with the provided one if the boolean is true or with the zero (the identity function) otherwise. We are now ready to write a functional version of the former calculate method:

public double calculate(double basic, boolean... bs) {
  return endo((Endomorphism<Double>) this::plusAllowance, bs[0])
  .add(this::plusBonus, bs[1])
  .add(this::plusTax, bs[2])
  .add(this::plusSurcharge, bs[3])
  .get()
  .apply(basic);
}

As anticipated, the main advantages of this second solution are the absence of both any mutable state and any alternative branch of evaluation. Nevertheless there is a further positive effect of this functional driven refactor that deserves to be underlined: removing the last apply() invocation from the former fluent invocation chain you can obtain a method returning a function for each of the possible 16 combinations of the 4 boolean values in the array. In this way, if for example you have to calculate many salaries for which only the allowance and the surcharge have to be applied you could obtain a function passing to that method a [true, false, false, true] array and then calculate all those salaries just by invoking multiple time apply() on the returned function with the different basic salaries.

Published at DZone with permission of its author, Mario Fusco.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Edmondo Porcu replied on Fri, 2013/03/15 - 3:07am

This is interesting, one of the best features I like about Scala and which is widely used in libraries as Scalaz is the support for implicit conversions, which allows Monoid to be composed, for example.  Is there anything similar planned in Java8?

Markus Schlegel replied on Wed, 2013/03/20 - 7:07am

The "simple but imperative" way is understandable for every programmer on first view - no need to explain anything, it's clear and understandable instantly. The functional version needs extra explanation and reading it is much harder. 

It's an interesting theoretical solution, but please don't do it in real life.

Mario Fusco replied on Wed, 2013/03/20 - 10:23am in response to: Markus Schlegel

Once again people confuse what they are used to see with what they believe is readable and understandable for the rest of the world. Here readability and understandavbility are very subjective and dependent on your background and mindset. Functional programming requires a different mindset indeed, but since Java is going to have lambda expressions maybe it is also arrived the time for Java developers to get more familiar with it.

While readability is subjective, there are some very objective and non-arguable advantages of the functional solution:

  • one single statement
  • one single branch of execution instead of 16
  • lower cyclomatic complexity
  • immutability

Edmondo Porcu replied on Wed, 2013/03/20 - 11:04am in response to: Mario Fusco

+100 for Functional programming :)

Jim LoVerde replied on Wed, 2013/03/20 - 12:05pm

To me, a simpler version that avoids defining new functional types would be something like this (assuming you define a zero method as well):

public double calculate(double basic, boolean... bs) {   
    return Arrays.asList(
            bs[0] ? this::plusAllowance : this::zero,
            bs[1] ? this::plusBonus : this::zero,
            bs[2] ? this::plusTax : this::zero,
            bs[3] ? this::plusSurcharge : this::zero
        )
        .stream().sequential()
        .reduce(basic, (result, rule) -> rule.apply(result));
}

And if streams supported filterWithIndex or reduceWithIndex, it would eliminate the ugly this::zero logic.  But "withIndex" versions of the functional methods aren't "pure" so they aren't supported.  But we can force the issue a variable, ugly though it may be.

While not purely functional, I'd argue that it would be more understandable and maintainable by more developers.  (Note that the list of methods can be stored as a data member if you want to avoid instantiating the list each time and/or if you want to make the list of rules configurable.)

public double calculate(double basic, boolean... bs) {
    int index = 0; // because we don't have withIndex methods    
    return Arrays.asList(
            this::plusAllowance,
            this::plusBonus,
            this::plusTax,
            this::plusSurcharge
        )
        .stream().sequential()
        .filter((rule) -> bs[index++])
        .reduce(basic, (result, rule) -> rule.apply(result));
}

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.