If Java was a Haskell

Haskell has the reputation of being one of the most advanced programming language of our time. Its main strengths are its functional paradigm, its purity which implies immutability, and a strong type system. I won’t expand on why these properties make it a powerful language, but in a nutshell they lead to short and expressive code, with a pretty darn good amount of reusability, without sacrificing the robustness of strong and static typing which is familiar to Java developers.

If you’d like to learn Haskell, I recommend starting with the wonderful Learn You a Haskell for Great Good. This introduction to Haskell is really pleasant to read, thanks to the detailed explanations and the cheeky humor, and it’s available online for free.

Very often, people recommend OO developers to forget anything they know in order to grasp the concepts of functional programming. I’ve even seen the term “unlearn” in some places. That may be a wise advice but as I learned about Haskell I couldn’t help comparing it with Java. Let’s see how far we would need to stretch the Java language in order to make it a Haskell.

Variables

The first thing that prevents Java from becoming a Haskell is its ever-present mutability. It’s relatively easy to address, just imagine that every single variable has to be final. In other words, every variable becomes a constant in a given scope. This scope can be a class, an object or just local to a method.

From there, all a programmer can do with a variable is binding it to a value, only once, then reading it as many times as necessary. An interesting consequence is that objects can then be created, but never modified, they’re immutable. In order to make Java a Haskell, just imagine immutability is the default and there is no other way. And that’s true even for local variables, so no more i++ folks.

Functions

Haskell is a pure functional language, it means everything can be thought in terms of input and output, like with mathematical functions. The first thing that comes to mind to a Java developer is the similarity with static methods. If you take for granted the total immutability mentioned above, static methods actually become pure functions (as long as they don’t contain I/O side effects which is another story).

Now we need to to beef up a bit these static methods. You may be aware of Project Lambda which will bring lambda expressions in Java 8. It will basically allow methods to take other methods as parameters. In other words, instead of taking only input values, a method will be able to take as a parameter some code to execute. Note that formally speaking a lambda expression is an anonymous function, however Java 8 will offer a special construct allowing to send a real named method as parameter for another method. So we’ll get more than just lambda expressions.

To bridge the gap with higher-order functions that Haskell and other functional languages offer, we also need the ability to return a method. As far as I know, this will also be possible in Java 8 by encapsulating the returned code into a Runnable or Callable. So Java 8 will pretty much allow for higher-order functions.

Example

Believe it or not, with total immutability and higher-order functions, Java would become a functional language. Objects would merely become data structures – I’ll cover this in another post – and imperative constructs such as loops would become useless. Think about it, even if we keep for in the language, how much can you do with it if you can’t mutate anything? Nada! Instead you’ll need to go the functional way, meaning recursion. For instance, to sum up the integers in an array, without mutating a variable, we’d need to do something like this:

int sum(int[] values) {
    return doSum(values, 0, 0);
}

int doSum(int[] values, int index, int accumulator) {
    if (index < values.length) {
        return doSum(values, index+1, accumulator+values[index]);
     }
     return accumulator;
}

The code above requires to pass the current index to look up. To avoid that we would need a way to extract the tail of the array. One way would be to wrap the array into an object also containing the current index and use this object instead of the plain array. That’s how the rest function (lisp name for tail) is implemented internally in Clojure. But in order to bring more Haskell flavor into our mutant Java, we will introduce some syntactic sugar called destructuring, which allows to automatically split an argument into multiple variables. Here is how it can transform our code:

int sum(int[] values) {
     return doSum(values, 0);
}

// notice how values is decomposed into head and tail
int doSum(int:int[] head:tail, int accumulator) {
    if (tail.length > 0) {
        return doSum(tail, accumulator+head);
    }
    return accumulator;
}

In this code, the function applied between the accumulator and each value is hard-coded, it’s ‘+’. Thanks to higher-order functions, we can replace it with any 2-argument function that we send as a parameter. Let’s do this and rename doSum into comprehend.

int sum(int[] values) {
    return comprehend(values, 0, (int x, int y) => x + y);
}

int comprehend(int:int[] head:tail, int accumulator, Callable function) {
    if (tail.length > 0) {
       // Note that I couldn't find how Java 8 will allow invoking a Callable
       // with some arguments so I made up the call method below
       int newAccumulator = function.call(accumulator, head);
       return comprehend(tail, newAccumulator, function);
    }
    return accumulator;
}

The next step would be to parameterize the comprehend method to allow for any type of accumulator, not just int. This way we can re-use comprehend to do other things, such as filtering a list. Here I will assume that primitives can be used as parameterized type in our new language.

int sum(int[] values) {
    return <int, int>comprehend(values, 0, (int x, int y) => x + y);
}

String[] filterStartsWith(String[] values, String prefix) {
    // the function below introduces a new syntactic sugar ++ which allows instantiating
    // an array which is the exact copy of x, with y appended to it
    Callable filter = (String[] x, String y) => (y.startsWith(prefix))? x++y : x};
    return <String, String[]>comprehend(values, new String[0], filter);
}

<E,T> comprehend(E:E[] head:tail, T accumulator, Callable function) {
    if (tail.length > 0) {
       T newAccumulator = function.call(accumulator, head);
       return comprehend(tail, newAccumulator, function);
    }
    return accumulator;
}

You can see how we reused our comprehend method for 2 very different things, summing the integers in a list and filtering the strings which start with a given prefix from a list of strings.

There is more…

At this point, we have a functional Java, that’s already a big step. Haskell provides more functional goodness that I won’t expand on. Here is a quick overview:

  • Laziness: functions and variables are not evaluated before they’re actually used. This even allows for infinite lists.
  • Function composition: like in mathematics you can build a function which is a chain of other functions.
  • Pattern matching and Guards to automatically branch code based on an input value (or a destructured input value)
  • where and let bindings to structure the code with temporary variables
  • List comprehension: this is such a common use case that Haskell has syntactic sugar for it, so there is no need of a comprehend function like we made
  • currying: internally every function takes a single argument, when there are 2 or more in your code, the compiler decomposes it into 2 or more functions. It’s easier to understand with an example, the function a + b can be decomposed into a function which takes an argument a and returns a function which adds a to its argument. Then we pass b to this new function. The cool benefit is the ability to pass around some partially applied function.

In my humble opinion, those are just nice secondary features of Haskell. Its true essence is made of the functional paradigm that I tried to cover in this post, and its type system which deserves another post.

4 thoughts on “If Java was a Haskell

    1. Thanks for commenting PiouPiou
      I’m not familiar enough with Scala but I guess you can replace Haskell with Scala in my article and most of it still works. To be clear, my goal was to anchor my recently acquired knowledge of Haskell by putting it in my own words, and definitely not to reinvent anything.

  1. Nice article! Makes me feel like learning me a Haskell.
    Instead of Runnable you’d rather use a Callable (as in Java Callable returns sthg where runnable don’t). And btw it’s not invoked with apply() but call() (and run() for a Runnable). Anyway we’ve got the idea!

    1. Thanks for the feedback. You’re right about Callable, it makes more sense and I updated the article accordingly. However I still couldn’t find any plan for Java 8 to allow arguments passed to the call() method.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>