Performance Zone is brought to you in partnership with:

Pierre-Yves Saumont has managed his own company for 15 years, specializing in natural language processing. At the same time, he has been the publishing manager for the French subsidiary of a leading American computer books publisher. He wrote about 30 books about computers and software development in Java. Since 2008, he works for Alcatel-Lucent Submarine Networks as an R&D Software engineer, and he is responsible for the architecture of several projects such as distributed application framework, functional framework and functional DSML (Domain Specific Modeling Language). He is also a double bass Jazz player. Pierre-yves has posted 8 posts at DZone. You can read more from them at their website. View Full User Profile

What's wrong in Java 8, part VI: Strictness

06.17.2014
| 7501 views |
  • submit to reddit

In a recent article (What's Wrong in Java 8, Part IV: Tuples), I said that there are no functions of several arguments. In other words, function arity is always one. This of course may have been seen as controversial. I also said that writing functions of several arguments is only syntactic sugar for either:

  • functions of one tuple argument, or

  • functions returning functions.

More precisely, a function of arity n is in fact either:

  • a function of one argument of type tuplen, or

  • a function of one argument returning a function of arity n – 1

In the second case, by recursion, we can see that a function of arity n may be converted into a function of arity 1. It can even be transformed into a function of arity 0 (i.e. a constant), which corresponds to total application of the function.

So what is in reality the difference between functions of tuples and functions returning functions? The difference is related to when arguments are evaluated.

In case of a function of tuple, there is really only one argument. So, what may appear as several arguments are values that are evaluated at the same time.

By contrast, if we translate what seems to be a function of arity n into a function of a function of arity n – 1, we are applying only one argument. So this argument is evaluated, but the others may remain non evaluated. In other words:

Integer f(Integer a, Integer b)

may represent a function of the product Integer x Integer, which is the set of all pairs (a, b) with a, and b being Integers, to Integer. In other words, the type of the argument of f is Integer x Integer and the return type is Integer

Or it may represent a function of the set Integer to the set of functions from Integer to Integer.

This is what we saw in a previous article:

(a, b) -> ...

May be replaced with:

a -> b -> ...

This is done through currying (see What's Wrong in Java 8, Part I: Currying vs Closures).

Note that the fact that the first function may be rewritten as the second does not mean they are equivalent. In other words:

f (a, b)

is different from

(f a) b

although they may produce the same result.

So what is really the difference between the two from the developer's point of view? The difference is in evaluation of the parameters.

Java is (mostly) a strict language

What this means is that in Java, things are generally evaluated as soon as they are referenced. This implies that some elements are evaluated although they might not be used. For example, if we write the following method:


Integer compute(Integer a, Integer b) {
  Integer result = ... // method implementation
  return result;
}

parameters a and b be will be both evaluated before the method implementation is executed. This may not seem a big deal here, but consider the following example:

public static Integer param() {
  return 9;
}

public static Optional<Integer> compute(Integer a, Integer b) {
  if (b == 0) {
    return Optional.empty();
  } else {
    return Optional.ofNullable(a / b);
  }
}

public static void main(String... args) {
  compute(param(), 3).ifPresent(System.out::println);
}

This code will print:

3

Now replace the implementation of param with:

public static Integer param() {
  throw new RuntimeException();
}

And change the main method to:

compute(param(), 0).ifPresent(System.out::println);

If we execute the program, we get:

Exception in thread "main" java.lang.RuntimeException

Although the implementation code does not use the first parameter (since only the if branch is executed) this parameter is evaluated, so an exception is thrown.

In Java, we are not given the choice. Method parameters are always evaluated before the method implementation is executed. In some languages, and in particular in functional languages, parameters may be evaluated only if needed, which is sometimes called "lazy" evaluation.

Is Java sometimes lazy?

Like all languages, Java is sometimes lazy. In fact, it would be much more difficult to write programs with a totally strict language.

The most well known lazy constructions in Java are the boolean operators && and ||. Unlike their binary counterparts & and |, which are strict, && and || parameters are only evaluated if necessary. But in Java, they are generally not called “lazy” operators, but “short circuiting” operators.

As a result of this laziness and of the strictness of method parameters evaluation, it is not possible to emulate the && and || operators with a Java method. (If you don't believe me, just give it a try.) The following trivial implementation is clearly broken:

public boolean or(boolean a, boolean b) {
  return a || b;
}

because b will always be evaluated, even if a is true. So if evaluating b throws and exception, this method will throw the exception even if a evaluates to true, although a && b won't and will quietly return true.

There are some other lazy constructions in Java, for example

  • the ternary operator ?:

  • if... then...else

  • the for loop

  • the while loop

  • the Java 8 Stream

  • the Java 8 Optional

Streams are lazy, and this is fundamental. The core principle of java streams is that they are not evaluated until a terminal operation is called. For more details, see the previous article (What's Wrong in Java 8, Part III: Streams & Parallel Streams).

Optional is lazy and it is also evaluated only when a terminal operation is called on it, although “terminal operation” does not belong to the Java 8 vocabulary about Optional. But remember from my previous article (What's Wrong in Java 8, Part IV: Monads) that Optional, as well as Stream, are monads. Optional is like a Stream of only zero or one element. That is why in some functional libraries or languages, Optional (or an equivalent type such as Option or Maybe) has a forEach method instead of the Java 8 Optional.ifPresent(). Oracle engineers probably chose ifPresent because they felt that forEach was suitable only if there was more that one element.

if...then...else is a lazy construct, because only one branch will be evaluated, depending on the condition. Once again, if...then...else may not be emulated with a method such as:

//
T ifThenElse(boolean condition, U if, V else)

because all three arguments (including if AND else) would be evaluated before testing the condition.

Perhaps it is not so evident, but Java loops are lazy constructs. Think about this:

for (int i = 0; i < 10; i++) {
  System.out.println(i);
}

This is equivalent to:

//
IntStream.range(0, 10).forEach(System.out::println);

To make it clear that the loop is a lazily evaluated structure, let's rewrite it that way:

for (int i = 0;; i++) {
  if (i < 10) System.out.println(i); else break;
}

This is equivalent to:

//
IntStream.range(0, Integer.MAX_VALUE).filter(x -> x < 10).forEach(System.out::println); 

The main difference is that with the for loop, the evaluation of the sequence of int is interleaved with the effect apply to each int. With streams, the evaluation of the sequence and the effect applied to each elements are decoupled. Both cases are based on lazy evaluation. And in fact we may build and process infinite streams and infinite loops only because both are lazily evaluated. Without lazy evaluation, we would have much trouble.

So the question is: Why can't we have lazy evaluation everywhere in Java? If Java is aiming at more functional programming, we need to be able to choose between strict and non-strict evaluation for method parameters.

Note: you may have noted that IntStream.range(1, Integer.MAX_VALUE) is not exactly equivalent to the for loop above, because the for loop is infinite. It is however possible to build an equivalent Stream, but we would have to use a slightly more complex construct. But the functional syntax we used is probably closer from what we intended that the for construct which index will eventually overflow.

What's next?

In the next article, we will focus on using the right types and will see that primitives are really something we should not be using. At least, our APIs should not expose them.

Previous articles

What's Wrong in Java 8, Part I: Currying vs Closures

What's Wrong in Java 8, Part II: Functions & Primitives

What's Wrong in Java 8, Part III: Streams & Parallel Streams

What's Wrong in Java 8, Part IV: Monads

What's Wrong in Java 8, Part IV: Tuples

Published at DZone with permission of its author, Pierre-yves Saumont.

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

Comments

Robert Saulnier replied on Tue, 2014/06/17 - 8:46am

    public boolean or(BooleanSupplier a, BooleanSupplier b) {
        return a.getAsBoolean() || b.getAsBoolean();
    }

Pierre-yves Saumont replied on Tue, 2014/06/17 - 10:02am in response to: Robert Saulnier

You changed the signature of the method. In other words, you are solving a different problem!

Robert Saulnier replied on Tue, 2014/06/17 - 11:10am in response to: Pierre-yves Saumont

You wanted to lazily evaluate the args, so we need to pass in something we can lazy eval.

Comment viewing options

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