XHTMLBoy's Website

You are on a website dedicated to the enthusiasts of (valid) XHTML, and of beautiful mechanics.
Back to index

Type-dependent instances in Java

Today, I present you with a powerful tip... so strong that I almost didn't share it. But when you want to become a champion, in a community other than boxing, you have to be able to take on the role of mentor and share the knowledge you have acquired over the years without limit. Watch your abs, we're going to build muscle, and not just the head, let me introduce "type dependent instances".

When you want to become a 10-engineer you have to master several skills. Firstly, you need to use a "business ready" language (like C# or Java, usually these kinds of serious languages have an "Enterprise" version), then, you have to be able to write software that crashes enough to ensure cash-money by doing maintenance, and finally, you have to be able to abstract enough not to repeat yourself... and yes, it is often said, software engineers are lazy! In this article, I propose you to discover an article which will make you save a colossal amount of time! Please note that all the examples presented here are from a case that actually happened. I swear on my mother's life (lol).

A brief background of the problem

This part could have been called "How I solved, with great intelligence, a very complex problem".

Beware, this first part is full of very complicated code, so don't panic if you don't understand everything at first reading.

While awkwardly trying to write software, I kept sadly running into NullPointerException. What a hell! Although Java could be a bit more expansive on the ability of a value to become null (as in Kotlin which is, to my knowledge, one of the only languages that handles explicit treatment of nullable values), the error is essentially my responsibility! As I am an engineer, I just need to write a function that can handle the presence or absence of an integer! Nice.

First, the parent type is described:

abstract class NullableInt {}

And then I can describe the two cases, the first being whether I have a value. It's quite simple, you just assign a value.

class Int extends NullableInt {
    private int value;
    public Int(int value) {
        this.value = value;
    }

    public String toString() {
        return "" + this.value;
    }
}

And the second, if I have no value! Which is even simpler!

// if I don't have any value
class NullInt extends NullableInt {
    public String toString() {
        return "Null";
    }
}

On the other hand, for the moment, I am no further ahead... indeed, I can do nothing with my incredible safety addition... don't panic, to make our class useful, we just need to be able to apply functions to our value, but ... what is a function in Java? It seems that there is, since Java8, a feature called Lambda... but let's be honest... who in real life wants to program using obscure Greek words? Fortunately, when you want to avoid greeknessense (γεια), you can use an even more greeknessense encoding presented in A Theory of Objects by Martin Abadi and Luca Cardelli.

Indeed, a "lambda" is nothing more than a class that implements a particular interface:

interface FunctionFromIntToInt {
  int call(int x);
}

We can now define, without much verbosity, two functions: Successor and Predecessor. The Successor function takes an integer and returns, as its name suggests, its successor.

class Successor implements FunctionFromIntToInt {
    public int call(int x) {
        return x + 1;
    }
}

The Predecessor function takes an integer and returns, as its name suggests, its predecessor.

class Predecessor implements FunctionFromIntToInt {
    public int call(int x) {
        return x - 1;
    }
}

Another common function is the identity function, which in the case of integers could have been written as the application of successor and predecessor (and vice versa) can be described even more easily in this way:

class Identity implements FunctionFromIntToInt {
    public int call(int x) {
        return x;
    }
}

With a tool to apply functions, we can now modify our API to be able to "surely" apply functions to our values. Nice. Indeed, we only need to add methods to our abstract class to be able to ... deal... with our null values:

abstract class NullableInt {
    abstract public NullableInt applyFunction(FunctionFromIntToInt f);
    abstract public int fold(FunctionFromIntToInt isNotNull, int isNull);

    public int getValueOrDefault(int defaultValue) {
        return this.fold(new Identity(), defaultValue);
    }
}

As you can see, our abstract functions are expressive enough to describe the getValueOrDefault function which is a little masterpiece of mechanics (and yes, I told you, this blog is for lovers of beautiful mechanics... and muscles). Now, our two classes must implement the abstract methods... and yes... otherwise the code will not compile!

Nothing could be simpler, each child class will take care to implement only what concerns it. So in the case where we have NullInt, it is sufficient to return a NullInt instance when applying a function. And in case of fold (a kind of Visitor to use the terminology of The Gang of Four, not to be confused with The Club of Five, GO DAGOBERT!), just return isNull.

class NullInt extends NullableInt {
    public NullableInt applyFunction(FunctionFromIntToInt f) {
        return new NullInt();
    }

    public int fold(FunctionFromIntToInt isNotNull, int isNull) {
        return isNull;
    }
    public String toString() {
        return "Null";
    }
}

Now that we take into account the case where the value does not exist, it is sufficient to implement the case for Int. Nothing could be simpler, when we want to apply a function... we just apply a function and for fold we execute isNotNull (which is a function):

class Int extends NullableInt {
    private int value;
    public Int(int value) {
        this.value = value;
    }
    public NullableInt applyFunction(FunctionFromIntToInt f) {
        return new Int(f.call(this.value));
    }

    public int fold(FunctionFromIntToInt isNotNull, int isNull) {
        return isNotNull.call(this.value);
    }

    public String toString() {
        return "" + this.value;
    }
}

We have all the ingredients to build pipelines of computations on integers that can be null and all... without any NullPointerException. This is extraordinary. Without further ado, I'll give you some exclusive production code that uses this clever approach.

public class ARealWorldService {
    public static void main(String args[]) {
      FunctionFromIntToInt succ = new Successor();
      FunctionFromIntToInt pred = new Predecessor();
      NullableInt myNullableInt = new Int(10);
      NullableInt myOtherNullableInt = new NullInt();
      System.out.println(myOtherNullableInt
            .applyFunction(succ)
            .applyFunction(pred)
            .applyFunction(succ)
            .getValueOrDefault(100)
      );
    }
}

Terrific.

When problems arise

After putting this (micro-)service into production (in a Docker that mounts a JVM) many of my colleagues came to me for help in refactoring their services... they had to deal with nullable strings, nullable doubles etc. When you want to manipulate a lot of nullable types, you realise that in fact... what you want... are the templates of C++. Indeed, without methods to generalise nullability... the growth of the number of classes required can quickly explode.

Fortunately... while immersing myself in existing Java code, I discovered a tool that allows me to save time. I called it "type-dependent instances", because the only name I could find in the documentation was "Generics" (and I didn't see a connection with Bernard Minet, a joke exclusively for French speakers).

type-dependent instances

Java has a way of describing type variables in the class definition... which will allow this type to be fixed (monomorphised) at instance time. For example, we could redeclare our function functionFromIntToInt in this way:

interface Function<In, Out> { Out call(In x); }
interface EndoFunction<T> extends Function<T, T> {}
interface FunctionFromIntToInt extends EndoFunction<Integer> {}

Surprisingly, this is a fairly well-known approach in Java (but I didn't know it) used to, for example... describe lists: List<A> where A is a (type) variable, but also to describe... optionality with Optional<A>. By the way, the ultimate form of refactoring my real-world-code would simply be to replace all my occurrences of NullableInt with Optional<Integer>. Wow.

Going further with type constraint

Incredible but the magic doesn't stop here. Indeed, it is possible to constrain the type of a type variable, using the syntax : Class<T extends S> which means the class Class is parameterised by a T which is a subtype of S. It is called a "Bounded Type Parameter", but as I find the name unclear, I suggest using the term: "type class", because the type is constrained by a class (or an interface, but it is the same thing). It is also possible to add several separate constraints using &. Incredible. The documentation talks about "Multiple Bounds" but I also find this name unclear, so I propose to call it "type family" because all the constraints form a family.

To conclude

Incredibly, we have discovered a rather unknown feature of Java to facilitate refactoring. To summarise:

Some people will tell me that dependent types, type classes and type families already exist in programming terminology but as mentioned previously let's be honest, who in real life wants to program using obscure Greek words?

I hope you enjoyed reading this, and see you soon for new articles!