How can a concise closure be written for a generic method?

325 Views Asked by At

I want to write an implementation of a functional, non-generic interface which has a generic method. The implementation needs to be an inline closure and concise.

As a simplified example

@FunctionalInterface interface Fn {
    <R> R fn(R arg);
}
public class Scratch {
    Fn id = arg -> arg;
    //Fn nul = arg -> null;
    //Fn requiresNonNull = ...
}

which gives

/Scratch.java:5: error: incompatible types: invalid functional descriptor for lambda expression
    Fn id = arg -> arg;
            ^
    method <R>(R)R in interface Fn is generic
  where R is a type-variable:
    R extends Object declared in method <R>fn(R)
1 error

(In reality the parameter would be a generic interface with a methods that had a return type of R.)

Is there a workaround without heading back to the verbosity of an anonymous inner class?

There is an apparently similar question, "Cannot convert functional interface with generic method into lambda expression", but that stems from using a type parameter called Integer instead of something conventional like T, and Jon Skeet's accepted answer says he doesn't know a solution to my problem.

There is also a long discussion, "Functional interface confusion", that fails to answer this question. It can't be "a verbose anonymous inner classes is best here", can it?

2

There are 2 best solutions below

2
On

Generic lambdas aren't legal, but generic method references are. You can cut down a bit on the verbosity of anonymous classes by creating helper methods instead:

public class Scratch {
    Fn id = Scratch::id;
    Fn nul = Scratch::nul;
    Fn requiresNotNull = Objects::requireNonNull;

    private static <R> R id(R arg) {
        return arg;
    }

    private static <R> R nul(R arg) {
        return null;
    }
}
1
On

After much experimentation and indirection I have a solution. I have been less successful with naming.

Here's the idea

  • Neither the functional interface nor the single abstract method has a type parameter.
  • The functional interface receives a consumer with a type parameter but that is wildcarded in the method parameter.
  • The consumer is just a piece of internal gubbins, but it does have that type parameter. It is used to stash the result whilst execution returns through the enclosing function.
  • The consumer itself receives a functional interface containing the actual business function instance which is of a type parameterised type.
  • There's a default method that ties things together, including creating the consumer.

Clear? [rhetorical]

So instead of being able to write

Fn id = arg -> arg;

we can at least write

Fn id = q -> q.q(arg -> arg);

which is a lambda lambda factory.

We seem to have run out of syntax and can't write something like

Fn id = Fn.Consumer::q(arg -> arg); // not valid syntax!

Altogether (with a main to show I'm not cheating)

import java.util.concurrent.atomic.*;

@FunctionalInterface interface Fn {
    interface Instance<R> {
        R fn(R arg);
    }
    interface Consumer<R> {
       void q(Instance<R> gn);
    }

    void consume(Consumer<?> consumer);

    default <R> R fn(R arg) {
        AtomicReference<R> result = new AtomicReference<>();
        this.consume((Instance<R> instance) -> { result.set(instance.fn(arg)); });
        return result.get();
    }
}

public interface Scratch {
    Fn id = q -> q.q(arg -> arg);
    Fn nul = q -> q.q(arg -> null);

    public static void main(String[] args) {
        String idStr = id.fn("cruel");
        String nulStr = nul.fn("cruel");

        System.err.println(idStr);
        System.err.println(nulStr);
    }
}

I don't think I've exploited any lack of soundness in the type system.

(I should probably add a more complicated example to the question to show why you might want to do this.)