Damien is a developer who spent 10 years in consulting companies in France and Canada before joining Kijiji.ca, the leading classifieds web site in Canada and a branch of eBay classifieds. He's been working mostly with Java but is getting more and more interested in emerging languages on the JVM, especially Clojure. Damien is a DZone MVB and is not an employee of DZone and has posted 3 posts at DZone. You can read more from them at their website. View Full User Profile

If Java was a Haskell

01.29.2013
| 5064 views |
  • submit to reddit

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, Runnable function) {
    if (tail.length > 0) {
       // Note that I couldn't find how Java 8 will allow running a Runnable
       // with some arguments so I made up the apply below
       int newAccumulator = function.apply(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
    Runnable 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, Runnable function) {
    if (tail.length > 0) {
       T newAccumulator = function.apply(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.

Published at DZone with permission of Damien Lepage, author and DZone MVB. (source)

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

Comments

Dapeng Liu replied on Tue, 2013/01/29 - 10:52am

there is one thing you can't go over, java doesn't have proper tail recur ... your stack will blow up somewhere around 1k ~ 10k nested method invocations

Damien Lepage replied on Tue, 2013/01/29 - 11:12am in response to: Dapeng Liu

That's very true. But I think I was not clear on one thing. My intention with this article was just to explain Haskell concepts to Java developers, digesting these same concepts myself in the process. So there is no porting constraint in the picture.

For those who'd like to run Haskell on the JVM however, you can take a look at Jaskell.

Mansur Ashraf replied on Tue, 2013/01/29 - 2:02pm in response to: Damien Lepage

If Java was Haskell it would be called Scala :-)

Damien Lepage replied on Tue, 2013/01/29 - 3:18pm in response to: Mansur Ashraf

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. But thanks for reading and commenting.

I suspect my next article around Haskell type system will not appear as familiar to Scala developers, since Scala seems to differ quite a bit from Haskell in this area according to this stackoverflow thread .

Ingo Wechsung replied on Wed, 2013/01/30 - 3:08am

The coolest thing in Haskell is the type system, though.

It is too bad that, in this context, the alternative to the untyped Jaskell scripting language is not even mentioned: Frege (https:://github.com/Frege) is a very Haskell like language that compiles to Java. It is non-strict, pure and has a type system like Haskell 2010 plus higher rank types. 


Damien Lepage replied on Wed, 2013/01/30 - 10:09am in response to: Ingo Wechsung

Thanks for pointing this out. I wasn't aware of Frege. Regarding Haskell type system I'll try to cover it properly in my next post, to the best of my ability.

Ingo Wechsung replied on Thu, 2013/01/31 - 3:25am in response to: Damien Lepage

Great! Looking forward to read your next post.

Comment viewing options

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