Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there any way to circumvent the typedness of lambda expressions?

This is a question that I wondered about since the lambdas had been introduced in Java, and inspired by a related question, I thought that I might bring it up here, to see whether there are any ideas.

(Side notes: There is a similar question for C#, but I did not find one for Java. The questions for Java about "storing a lambda in a variable" always referred to cases where the type of the variable was fixed - this is exactly what I'm trying to circumvent)


Lambda expressions receive the type that they need, via target type inference. This is all handled by the compiler. For example, the functions

static void useF(Function<Integer, Boolean> f) { ... }
static void useP(Predicate<Integer> p) { ... }

can both be called with the same lambda expression:

useF(x -> true);
useP(x -> true);

The expression will once manifest itself as a class implementing the Function<Integer,Boolean> interface, and once as a class implementing the Predicate<Integer> interface.

But unfortunately, there is no way of storing the lambda expression with a type that is applicable to both functions, like in

GenericLambdaTypelambda = x -> true;

This "generic lambda type" would have to encode the type of the method that can be implemented by the given lambda expression. So in this case, it would be

(Ljava.lang.Integer)Ljava.lang.Booleanlambda = x -> true;

(based on the standard type signatures, for illustration). (This is not completely unreasonable: The C++ lambda expressions are basically doing exactly that...)


So is there any way to prevent a lambda expression being resolved to one particular type?

Particularly, is there any trick or workaround that allows the useF and useP methods sketched above to be called with the same object, as in

useF(theObject);
useP(theObject);

This is unlikely, so I assume the answer will plainly be: "No", but: Could there be any way to write a generic, magic adaption method like

useF(convertToRequiredTargetType(theObject));
useP(convertToRequiredTargetType(theObject));

?


Note that this question is more out of curiosity. So I'm literally looking for any way to achieve this (except for custom precompilers or bytecode manipulation).

There seem to be no simple workarounds. A naive attempt to defer the type inference, by wrapping the expression into a generic helper method, as in

static <T> T provide()
{
    return x -> true;
}

of course fails, stating that "The target type of this expression must be a functional interface" (the type can simply not be inferred here). But I also considered other options, like MethodHandles, brutal unchecked casts or nasty reflection hacks. Everything seems to be lost immediately after the compilation, where the lambda is hidden in an anonymous object of an anonymous class, whose only method is called via InvokeVirtual...

like image 304
Marco13 Avatar asked Mar 31 '16 14:03

Marco13


2 Answers

I don't see any way of allowing a lambda expression that resolves to one particular functional interface type to be interpreted directly as an equivalent functional interface type. There is no superinterface or "generic lambda type" that both functional interfaces extend or could extend, i.e. that enforces that it takes exactly one parameter of exactly one specific type and returns a specific type.

But you can write a utility class with methods to convert from one type of functional interface to another.

This utility class converts predicates to functions that return booleans and vice versa. It includes identity conversions so that you don't have to worry about whether to call the conversion method.

public class Convert
{
    static <T> Predicate<T> toPred(Function<? super T, Boolean> func)
    {
        return func::apply;
    }

    static <T> Predicate<T> toPred(Predicate<? super T> pred)
    {
        return pred::test;
    }

    static <T> Function<T, Boolean> toFunc(Predicate<? super T> pred)
    {
        return pred::test;
    }

    static <T> Function<T, Boolean> toFunc(Function<? super T, Boolean> func)
    {
        return func::apply;
    }
}

The input functions and predicates are consumers of T, so PECS dictates ? super. You could also add other overloads that could take BooleanSuppliers, Supplier<Boolean>, or any other functional interface type that returns a boolean or Boolean.

This test code compiles. It allows you to pass a variable of a functional interface type and convert it to the desired functional interface type. If you already have the exact functional interface type, you don't have to call the conversion method, but you can if you want.

public class Main
{
    public static void main(String[] args)
    {
        Function<Integer, Boolean> func = x -> true;
        useF(func);
        useF(Convert.toFunc(func));
        useP(Convert.toPred(func));

        Predicate<Integer> pred = x -> true;
        useP(pred);
        useP(Convert.toPred(pred));
        useF(Convert.toFunc(pred));
    }

    static void useF(Function<Integer, Boolean> f) {
        System.out.println("function: " + f.apply(1));
    }
    static void useP(Predicate<Integer> p) {
        System.out.println("predicate: " + p.test(1));
    }
}

The output is:

function: true
function: true
predicate: true
predicate: true
predicate: true
function: true
like image 139
rgettman Avatar answered Oct 09 '22 17:10

rgettman


Suppose in a shiny future (say Java 10 or 11) we have true function types which allow to specify a function without forcing it to be of a particular conventional Java type (and being some kind of value rather than an object, etc.). Then we still have the issue that the existing methods

static void useF(Function<Integer, Boolean> f) { ... }
static void useP(Predicate<Integer> p) { ... }

expect a Java object implementing a conventional Java interface and behaving like Java objects do, i.e. not suddenly changing the result of theObject instanceof Function or theObject instanceof Predicate. This implies that it will not be the generic function that suddenly starts implementing the required interface when being passed to either of these methods but rather that some kind of capture conversion applies, producing an object implementing the required target interface, much like today, when you pass a lambda expression to either of these methods or when you convert a Predicate to a Function using p::test (or vice versa using f::apply).

So what won’t happen is that you are passing the same object to both methods. You only have an implicit conversion, which will determined at compile-time and likely made explicit in byte code just as with today’s lambda expressions.


A generic method like convertToRequiredTargetType can’t work because it has no knowledge about the target type. The only solutions to make such a thing work are the ones you have precluded, precompilers and byte code manipulation. You could create a method accepting an additional parameter, a Class object describing the require interface, which delegates to the LambdaMetaFactory but that method would have to redo everything the compiler does, determining the functional signature, the name of the method to implement, etc.

For no benefit, as invoking that utility method like convertToRequiredTargetType(theObject) (or actually convertToRequiredTargetType(theObject, Function.class)) is in no way simpler than, e.g. theObject::test). Your desire to create such a method caused your weird statement “Everything seems to be lost immediately after the compilation, where the lambda is hidden in an anonymous object of an anonymous class” when actually, you have an object implementing a functional interface with a known signature and therefore can be converted as simple as function::methodName (where the IDE can complete the method name for you, if you have forgotten)…

like image 26
Holger Avatar answered Oct 09 '22 16:10

Holger