Under what conditions can I call a SAM-accepting method using a trailing lambda?

I’ve observed that when a library method accepts a SAM (Single Abstract Method) object as its last argument, I can either call the method using the SAM-constructor syntax or the trailing lambda syntax. (IntelliJ prompts me to use the latter.) Here’s an example using JavaFX:

// import javafx.beans.property.SimpleStringProperty
// import javafx.beans.value.ChangeListener

SimpleStringProperty("hi").addListener(ChangeListener { _, _, _ -> println("changed") })
SimpleStringProperty("hi").addListener { _, _, _ -> println("changed") }

Here’s another example using the Java standard library’s java.util.function.Function:

listOf(1, 2, 3).stream().map(Function<Int, Int> { it * it })
listOf(1, 2, 3).stream().map { it * it }

And yet, if I define my own function that takes a final Function argument, I can’t use trailing lambda syntax:

fun takesSAM(x: Int, f: Function<Int, Int>): Int = f.apply(x)

takesSAM(5, Function { it * it })
// takesSAM(5) { it * it } // compiler error

This is even true if takesSAM is defined in a separate library.

What’s the deal?

It’s only for Java interop. In Kotlin, use Kotlin’s function type to accept lambdas.

fun takesSAM(x: Int, f: (Int) -> Int): Int = f.apply(x)
takesSAM(5) { it * it }

SAM Conversions
Higher-Order Functions and Lambdas

Yes, if I am writing a Kotlin method that will only be called from Kotlin, that would definitely be my preference. :slight_smile: I should give more context to explain why I care about this.

I’m writing a URL-handling library in Kotlin, but it’s intended to be used by any JVM language. Here’s a piece of code from the HostedUri class, allowing the caller to transform the path component of a URL using a function:

/** Set path by running a transform on the parsed path. */
fun mapPath(f: Function<TextPath, TextPath>) = withPath(f.apply(path))

I could have it take a (TextPath) -> TextPath instead, but that would mean exposing the implementation detail of kotlin.jvm.functions.Function1 in the public API, which is less than ideal. Using Function makes the API friendlier to consumers written in Java and other JVM languages.

Hence, my question: Why can’t my Kotlin application that is calling this URL-handling library use trailing lambda syntax for a Function, given that it can use trailing lambda syntax for a Function when calling the Java Stream API?

Comment by Kotlin lead architect :here

What is the problem with exposing a type of the kotlin std lib? Your code already exposes the kotlin standard library so it does not create an additional dependency a consumer of your library has to deal with. Just out of interest is Function a custom interface (I can’t find it in the kotlin nor java stdlib)? How is this interface better than the one provided by kotlin? Also what happens if you need a function argument with a different argument count in the future?

It’s in the Java stdlib since 1.8: https://docs.oracle.com/javase/8/docs/api/java/util/function/Function.html

While I have my reasons for wanting to use this rather than a kotlin.jvm.functions.Function1, my question is really about why I can’t seem to call my own API with the same syntax as I can call Java APIs, even when I use this existing SAM class.

From a quick read, that only covers cases where someone makes a new interface in Kotlin, right? Function here is a part of the Java stdlib, and I can use trailing lambda syntax with it when calling Java APIs.

Followup – from discussion on the Slack, I can confirm that this is not KT-7770, and no one seems to be able to figure out why this doesn’t work. :man_shrugging:

You’re right, it’s not KT-7770, it’s https://youtrack.jetbrains.com/issue/KT-11129. The feature “SAM conversion for Kotlin functions” already works in 1.3.61 with the following compiler arguments:

compileKotlin {
    kotlinOptions.freeCompilerArgs = ["-Xnew-inference", "-XXLanguage:+SamConversionForKotlinFunctions"]

The plan is to enable it by default in 1.4 along with the new type inference.

1 Like