Blog

Be aware of the non-lazy nature of Java with functional programming

Xebia Background Header Wave

When you get into the habit of functional programming, you probably start to love the composability and the readability that it brings to your code base. You might start to see code more as a pipeline or “pieces of a puzzle” that you try to connect (or in other words: functional composition). When chaining methods in Java, you can sometimes run into unexpected behavior…

At the client I’m working, we make use of VAVR, a library that can help you out with many daily functional programming tasks. One of those is the Option class, which is a better version than the default Optional in Java (see: Why java.util.Optional is broken)

Today I ran into an issue where some parts of my mock were triggered that were not supposed to be triggered. It was a pretty easy flow:

  • Get order by criteria X
  • If the order cannot be found, try criteria Y
  • If the order still cannot be found, try criteria Z

Simple right?

@Test
public void test() {
    Option<String> value = Option.<String>none()
        .orElse(doSomething("step 1"))
        .orElse(doSomething("step 2"))
        .orElse(doSomething("step 3"));
    System.out.println(value);
}
public Option<String> doSomething(String message) {
    System.out.println(message);
    return Option.some(message);
}

What will be the output of the snippet above?

step 1
step 2
step 3
Some(step 1)

You can see that the end result is eventually Some(step 1), but in the mean-time ALL the potential logic of all the other steps have been executed too! That’s definitely not what I intended when I wrote the above code…

How can we fix this? By returning functions, whom are lazy by default:

@Test
public void test() {
    Option<String> value = Option.<String>none()
        .orElse(doSomething("step 1"))
        .orElse(doSomething("step 2"))
        .orElse(doSomething("step 3"));
    System.out.println(value);
}
public Function0<Option<String>> doSomething(String message) {
    return () -> {
        System.out.println(message);
        return Option.some(message);
    };
};

The output that is generated:

step 1
Some(step 1)

Lesson learned? The mock saved my day here. It made me aware of things that were going on, that were definitely not intended. This would have probably been much harder to figure out on production. Also it’s another reason why languages like Scala are better suited for functional programming on the JVM, as there’s now an increased cognitive load on the developer that is not helping him or her in any way. Why would you even need to be aware of this in non-laziness in Java?

Just look at the Scala version:

def doSomething(message: String): Option[String] = {
  System.out.println(message)
  Some(message)
}
def main(args: Array[String]): Unit = {
  val value = None.orElse(doSomething("step 1"))
      .orElse(doSomething("step 2"))
      .orElse(doSomething("step 3"))
  print(value)
}

Same flow and no cognitive load on the developer. It’s a huge benefit if you write in a language that has certain fundamentals in place regarding laziness (like Scala). It makes you able to focus on flow and business logic instead of low-level details. It doesn’t add anything of value to the programmer that needs to implement features for a specific business domain, unless that business domain is highly technical, where control of low-level concerns are important.

Why does it work in Scala straight away? Because of something called “by-name parameters”, which is a core feature of the language and makes lazy evaluation a breeze. If you want more information on that topic, see: https://docs.scala-lang.org/tour/by-name-parameters.html

Hope this was useful! If you have any questions, please let me know!

Questions?

Get in touch with us to learn more about the subject and related solutions

Explore related posts