Lambdas in java are based on the idea of functional interfaces which we define as an interface with a single abstract method.

Here we describe a few of the shortcomings of the functional interface approach taken by java to implement lambdas.

Syntax

We take the Predicate function defined in the standard library as an example. Predicate as a functional interface provides the abstract method test:

@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
}

Filters are a common use case of predicate functions and can be implemented as follows:

static <T> List<T> filter(List<T> xs, Predicate<T> p) {
    // ... impl
    return xs;
}

Syntax wise the reader of the filter function signature is unaware that p is a lambda construct. Even if the reader has a hint that Predicate might be a function there’s no information about the “shape” of the function.

We can update the syntax to clearly identify lambdas and their “shape”. For example:

static <T> List<T> filter(List<T> xs, T -> Boolean p) {
    // ... impl
}

It’s now clear that p is a function and that it’s a function that receives a type T and returns a type Boolean.

Function application

The functional interface approach also hinders code re-use and function composition. Currently, the application of a function requires the name of the abstract method defined in the interface.

For instance, function composition can be declared as follows:

static <A, B, C> Function<A, C> compose(Function<A, B> f1, Function<B, C> f2) {
    return (A a) -> f2.apply(f1.apply(a));
}

The current approach binds the interface Function and its method apply which decreases code re-use. If we have a function with the same shape in our code base but with a name other than apply we cannot use compose directly.

On the other hand, this restriction is lifted if function application in lambdas is modified to follow the rules of regular methods.

Piggybacking in our made up lambda syntax from the previous example and removing the interface method during function application we can define composition as follows:

static <A, B, C> A -> C compose(A -> B f1, B -> C f2) {
    return (A a) -> f2(f1(a))
}

After the changes, any function in our code base that satisfies the arity and types of compose can be composed.

Package java.util.function

The package java.util.function illustrates the problem of diminished opportunity for code re-use and the increase of cognitive load.

The documentation for java 8 describes 44 function interfaces. Is the package lacking in different types of functions? Or, 44 functions is all we need?

Take Function<T, R> as a base case. What if we need a function that accepts 2 parameters? BiFunction<T, U, R> is provided. What if we need 3 parameters? There’s no TriFunction<T, U, R, S> in the standard library and we’re on our own.

The situation is made worse if we count the implementation specific to primitive types: IntFunction<R>, LongFunction<R> and etc.

Partial application

The functional interface approach also does not support partial application of functions.

Not surprisingly, this won’t compile:

BiFunction<Integer, Integer, Integer> f2 = (i, j) -> i + j;
Function<Integer, Integer> f = f2.apply(1);

In our hypothetical syntax we can make the case for:

Integer -> Integer -> Integer f2 = (i, j) -> i + j;
Integer -> Integer f1 = f2(1);
int r = f1(2); // r is set to 3

Recursion

The function interface approach does play well with recursion. We need some form of indirection to implement the following idea:

void foo() {
    Function<Integer, Boolean> f = i ->
        i <= 0 ? true : f.apply(i - 1);
    //                  ^ there's no reference to `f`
    f.apply(5);
}

Exception propagation

Typed exceptions are also not propagated.

The following code won’t compile:

void bar() throws Exception {
    Optional.of(10).map(this::baz);
}

int baz(int i) throws Exception {
    if (i < 0) throw new Exception("");
    return i;
}

Our initial assumption is that baz throws an Exception, and thus it would be correct to expect that bar also throws an exception since baz is used in the map operation by bar.