eclipse-archived/ceylon

Java SAM interface conversion restrictive when function has multiple SAM arguments

Closed this issue · 4 comments

Per the tour, when using a Java function that takes a SAM interface as an argument, the Ceylon typechecker treats it as having two versions—one that takes the interface as its argument and one that takes a Ceylon Callable type as its argument. When the Java function takes more than one SAM interface argument, one must pass all SAM interfaces or all Callable types.

For example, given this Java class:

import java.util.function.Predicate;

public class PredicateUser {
    public static <T, U> boolean usePredicates(Predicate<T> predicateT, T t, Predicate<U> predicateU, U u) {
        return predicateT.test(t) && predicateU.test(u);
    }
}

This happens in Ceylon code:

import java.util.\ifunction {
    Predicate
}

shared void predicate() {
    value predicateT = object satisfies Predicate<Boolean> {
        shared actual Boolean test(Boolean t) => t;
    };
    function predicateU(Boolean u) => u;
    
    // These work fine
    PredicateUser.usePredicates(predicateT, true, predicateT, false);
    PredicateUser.usePredicates(predicateU, true, predicateU, false);
    
    // These fail with "Boolean(Boolean) is not assignable to Predicate<Boolean>?"
    PredicateUser.usePredicates(predicateT, true, predicateU, false);
    PredicateUser.usePredicates(predicateU, true, predicateT, false);
}

For the real-world use case, I ran into this while seeing how to use Spring WebFlux from Ceylon:

    shared RouterFunction<out Object> route() {
        // Fails because the first value is a SAM interface type and the second is a Callable
        return RouterFunctions.route(RequestPredicates.get("/"), index);
    }
    
    shared Mono<ServerResponse> index(ServerRequest request) {
        return nothing;
    }

The solution would be for the typechecker to act like the Java function has 2^n overloads, but that may not be tenable.

Yeah well, we really didn't want to generate a combinatorial profusion of overloaded methods.

And I think it's fine: it's usually trivial to turn a SAM into a function. In your case

RouterFunctions.route(RequestPredicates.get("/").test, index);

should work, right?

Yep, that works, thanks! I don't suppose the compiler knows enough in this instance to be able to suggest that the developer can just add .test, right?

Rm, possibly, but it's probably not completely trivial, considering how complicated all the overload resolution stuff is.

Makes sense, thanks.