简体   繁体   中英

How to solve the lambda / SAM problem in Kotlin multiplatform library projects?

I have a problem with SAMs in Kotlin. I'm working on a library which has functions which accept a lambda. My problem is that I can't simply write this:

fun myFun(someLambda: (A) -> B) {
    // ...
}

because if a Java user would like to call it they would need to pass a Function1 to it which is not very good UX. If I create a SAM instead:

fun myFun(someSam: Function<A, B>) {
    // ...
}

then it is cumbersome for Kotlin users. What I've been doing so far is that I went the SAM way and added an extension function for all of these functions for Kotlin users which just translates to the SAM function:

inline fun <A, B> MyClass.someFun(crossinline fn: (A) -> B) {
    return someFun(object : Function<A, B> {
        override fun accept(value: A): B {
            return fn.invoke(value)
        }
    })
}

This method comes with a lot of boilerplate and it is also hard to maintain. Is there a better alternative to solve this problem?

In kotlin 1.4 you can now declare functional (SAM) interfaces like this:

fun interface MyFunction {
    fun doSomething(input: Int): Boolean
}

Note the "fun" keyword in front of the interface declaration. This will allow using a lambda from both kotlin and java.

See also https://kotlinlang.org/docs/reference/fun-interfaces.html

I just added convenience functions that allowed me to easily convert the Java functional interfaces to their Kotlin counterpart, but put them in back-ticks (which I don't like to see in regular Kotlin code (in contrast to tests) ;-)), eg:

fun <T> `$consume`(consumer: Consumer<T>): (T) -> Unit = consumer::accept
fun <T, R> `$`(func: java.util.function.Function<T, R>): (T) -> R = func::apply
fun <T, U, R> `$`(func: java.util.function.BiFunction<T, U, R>): (T, U) -> R = func::apply
// etc.

Assuming you have the following functions in Kotlin:

fun doSomething1(c : (String) -> Unit) : String = TODO()
fun doSomething2(f : (String) -> String) : String = TODO()
fun doSomething3(f : (String, String) -> String) : String = TODO()

Usage from Java could then look like:

doSomething1($consume((e) -> System.out.println(e)));
doSomething2($((e) -> e + "ok"));
doSomething3($((e1, e2) -> String.join(", ", e1, e2)));

Note that I used $consume to overcome the ambiguity regarding Function<T, R> . You could also use $ for the consumer, but then you would need either curly brackets or would have to cast it to the Consumer<T> -interace in order to take advantage of method references, eg:

doSomething1($((e) -> { System.out.println(e); }));
doSomething1($((Consumer<String>) System.out::println));

The main benefit of using $ with the back-ticks is, that in Kotlin you will probably not use it, as it isn't so easily writable (who starts with back-ticks when calling functions?) and code completion doesn't suggest it that easily.

Furthermore the $ -sign is directly callable on the Java side without problem.

Maybe you want to mix your overloading functions with this so you do not need to implement all the overloading code itself and put those functions in its own library available for Java developers?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM