Search code examples
lambdarx-javakotlinrx-kotlin

Behavior with Kotlin Higher-Order Functions and Single-method interfaces?


I was having some issues using RxJava and Kotlin earlier. I made some interesting discoveries which I'm still puzzled about.

There is the simple Func1 interface in RxJava

public interface Func1<T, R> extends Function {
    R call(T t);
}

I was trying to add an extension method to an Observable, also an RxJava class. This would collect the emissions into a Google Guava ImmutableListMulitmap using a Func1 to map the key off each item.

fun <K,T> Observable<T>.toImmutableListMultimap(keyMapper: Func1<T, K>): Observable<ImmutableListMultimap<K,T>> {
    return this.collect({ ImmutableListMultimap.builder<K,T>()},{ b, t -> b.put(keyMapper.call(t), t)}).map { it.build() }
}

When I tried to invoke this extension method I could not get it to compile, and it was not understanding the lambda expression at all.

ScheduledItem.all.flatMap { it.rebuildSoftTransactions }
.toImmutableListMultimap { it.id /*compile error */ } .cache()

However, the strangest thing happened when I modified the extension method to use the function type.

fun <K,T> Observable<T>.toImmutableListMultimap(keyMapper: (T) -> K): Observable<ImmutableListMultimap<K,T>> {
    return this.collect({ ImmutableListMultimap.builder<K,T>()},{ b, t -> b.put(keyMapper(t), t)}).map { it.build() }
}

And then everything compiled fine. But this is what puzzled me: How come it did not infer the lambda onto the interface? When I use the standard map() method on the Observable it infers the lambda just fine using the curly bracket { } syntax. But why does it not work for my extension method above?


Solution

  • The SAM conversion (converting a lambda into a function type) currently works only for methods written in Java. Kotlin has proper function types, so there is no need for SAM conversion - you can declare the parameter as a function type directly (which works, as you have observed).

    Observable.map() is written in Java, so the SAM conversion is applied. Your extension function is written in Kotlin, so it's not.