Locked to type-system in Java? Go functional!

Java 8 is out and along with it comes many new libraries for JDK, not least the long-waited lambda-expression from the functional world of programming. But how will Java 8 and lambda-expression help us to solve problems and limitations with Java's type-system? Here comes a real-life example.

KantId is a lightweight open-source project for handling id-numbers in different countries. It's developed by employees in Kantega and it's design emphasizes the functional approach. But more than that, it gave us an important and practical lesson about using object-oriented languages with ideas from functional programming, and some thoughts about where Java, and the way we compose our programs, could be heading.

Problem - with types and inheritance

Example for ID numbers, Norwegian and Italian

For Norwegian personal identity numbers the first 6 digits in format "DDMMYY" convey birth date, and location is not expressed:

12121258771

Italian personal identity numbers convey location and birth date in the format "SSSNNNYYMDDZZZZX" where location is expressed with 4 digits "ZZZZ" (the example person was born in Milan) and birth date with 5 digits in format "YYMDD".

MRTMTT25D09F205Z

When first designing our library, we soon found out that there is a great variation in what information personal numbers - or id-numbers - contain. Clearly there were also some common functionalities, which we wanted to encapsulate into a common type.

In short, the situation was that we needed:

  1.  A kind of a super type for common functionalities
  2.  A combination of many different special functionalities for each country
  3.  A type-system which is flexible enough for extending the library

And there lies the problem.

How can you at the same time implement common functionalities into one super-class, and combine many different implementations of interfaces under that class? Without locking users who want to extend and use your library?

As you can see above, one requires a hierarchy only for representing the two example IDs, and there are hundreds of definitions for personal numbers. This would apparently lead to an exponential growth of types.

public interface IdNumber {
    boolean isValid();
}

public interface BirthDateAware {
    LocalDate getBirthDate();
    Period age();
}

public interface LocationAware {
    String placeOfBirth();
}

public class ItalianIdNumber implements BirthDateAware, LocationAware {
...
}

public class NorwegianIdNumber implements BirthDateAware {
...
}

Either we have to have a common super type with all possible interfaces,or lots of different types for each possible combination of interfaces. Would that be an elegant and flexible solution, and wouldn't it lead either to type-casts or over-complex type hierarchy?

And how would someone, who wants to extend our library do that? Especially when we don't actually know very much about what kind of behavior is needed. All that sounded like an mission impossible with the type system in Java.

Solution - go functional

If we could just provide a base-class, or dispatcher-class for users, with methods that take functions as parameters, then whoever, who wanted to use or extend our class, could just pass in those functions he/she wanted, and avoid almost all limitations with type-hierarchies. Also we could provide some common functionalities in our base-class, to avoid code duplication spreading to all implementing- or client-classes.

Forget class-types and complex hierarchies, use higher order functions and just pass in the function you need!

This is actually nothing new, and we were not re-inventing the wheel here. Some people who are more familiar with FP call this approach "Hole in the middle"-pattern or just simply higher order function. It has also been possible to implement with Java prior version 8, but with lambdas it's easier and requires less code.

Common base class, pass functions in

public class IdNumber {

  public boolean isValid(final Predicate validityTest) {
      return validityTest.test(this);
  }

  public Optional birthday(final Function> birthDayFunction) {
      return birthDayFunction.apply(this);
  }

  public Optional age(final Function> birthDayFunction) {
      return birthDayFunction.apply(this)
        .map((birthday) -> birthday.until(now()))
        .map((period) -> period.isNegative() ? ZERO : period);
  }
}

Your implementation, choose which functions you need, and implement them.

public class NorwegianIdNumber extends IdNumber {

  public static boolean valid(final IdNumber idNumber) {
      return parse(idNumber.getIdToken()).isValid();
  }

  public static Optional birthday(final IdNumber idNumber) {
      return parse(idNumber.getIdToken()).getBirthday();
  }
}

 

Then just use it.

 

new NorwegianIdNumber("19071279718").isValid(NorwegianIdNumber::valid);

new ItalianIdNumber("MRTMTT25D09F205Z").isValid(ItalianIdNumber::valid);

new NorwegianIdNumber("19071279718").birthday(NorwegianIdNumber::birthday);

new ItalianIdNumber("MRTMTT25D09F205Z").age(ItalianIdNumber::birthday);

 

This approach lets you achieve the needed result with less code and more expressiveness, as you don't need to implement a whole class that conforms to an interface to provide the desired behaviour for the caller. By passing a simple function definition you can choose which functionalities your implementation will provide, without depending on specific type. You are free to implement just your subset of functionality if that's what you want, or everything defined in our base-class.

As a conclusion, code is often easier to change and test, more expressive and more flexible with usage of higher order functions.

But what about testing, should we use mocking?

Since we are more interested in functionalities than types, we can actually forget mocking almost completely, because we don't any longer want to mock types but functions. And how can we achieve that? Easily, with lambdas! (Test below also demonstrates usage of new date and time API)

@Test
public void ageIsCalculated_FromBirthday() {
    LocalDate thirtyFifeYearsOneMonthAgo = now().minusYears(35).minusMonths(1);
    Function> birthdayFunction = (idNumber) -> Optional.of(thirtyFifeYearsOneMonthAgo); 

    Period age = forId("123456-123K").age(birthdayFunction).get();

    assertThat(age.getYears(), is(35));
    assertThat(age.getMonths(), is(1));
}

Instead of mocking classes, we just send in "mocked"-function with lambda-expression which gives you the implementation that you need under testing. Simple as that.

Conclusion

Although this example used here is from a real-world project it's rather naive and simple. But imagine how complex a library without lambdas would have been. Instead of type tokens, factories, type hierarchies, class path discovery etc. lambdas simplified this drastically.

This approach also gives us an insight into some new tools we get with Java 8, and more than that, it illuminates that changes which come along with Java 8 and lambdas, will lead to big changes in the way we compose our programs. Just look at the example above, how the need for mocking disappeared with the functional approach.

Java 8 will be more than a collection of new libraries and lambda-expression. It will be a change in the way we think about OO. And that will be good!

Litt om forfatteren

comments powered by Disqus