Return type inferred from the lambda parameter return type


#1

I’m wondering if there is any way how Kotlin can infer the return type of the function based on its lambda parameter. I have the following code in Java:

private Map<Class<?>, Object> map = new HashMap<>();

private <T> T getInstance(Class<T> clazz) {
    Object value = map.get(clazz);
    return (T)value;
}

public <T, R> R process(Class<T> clazz, Function<T,R> f) {
    T instance = getInstance(clazz);
    return f.apply(instance);
}

@Test
public void testFoo() {
    map.put(Integer.class, 42);
    double doubleValue = process(Integer.class, x -> x + 13.2);
    System.out.println("Got double result "+doubleValue);
}

This works nicely and I’m trying to recreate a similar thing in Kotlin. This is what I have so far

var map = mapOf<KClass<out Any>, Any>().toMutableMap()

fun <T : Any> getInstance(clazz : KClass<in T>) : T {
    val instance = map[clazz]
    if (clazz.isInstance(instance)) {
        return instance as T
    } else {
        throw IllegalArgumentException("No instance for $clazz")
    }
}


inline fun <reified T : Any, R> process( noinline f : (T) -> R): R {
    val instance = getInstance(T::class)
    return f(instance)
}

class Test {
    @Test
    fun test() {
        map.put(Integer::class, 42)
        val doubleValue = process<Integer, Double> { it.toDouble() + 13.2 }
        println("Got double result $doubleValue")
    }
}

Now this works great, but I would like to make it even better - ideally I would like to omit the requirement to specify the output type in the process function - so instead of writing
val doubleValue = process<Integer,Double> { it.toDouble() + 13.2 }
I would like to write just
val doubleValue = process<Integer> { it.toDouble() + 13.2}
and infer the return type from the return type of the lambda itself. Is this doable? Thoughts? Ideas?


#2

Tried adding reified to R?


#3

That doesn’t help - the reification of the type is not really needed in this case.


#4

Try currying it. Your process function would have just one type parameter. It would return another function, which has the second type parameter. Just an idea, didn’t try it.


#5

Maybe something like this could work (still didn’t try it, just an idea):

inline fun <reified T : Any> process() :  Processor<T> {
    return Processor(getInstance(T::class))
}
class Processor<T>(private val value: T) {
    operator fun <R> invoke(f: (T) -> R) = f(value)
}

You might need to invoke it with implicit parenthesis like so:

process<Integer> () ({
    it.toDouble() + 13.2
})

I don’t know whether it will be parsable without the parenthesis.


#6

Indeed, the problem is that the option is either inference, or explicit parameters, but not combination. The following call should also work (of course you now might want to rename it):

val doubleValue = process { it:Int -> it.toDouble() + 13.2}