SAM Conversion - why does this example not compile?


#1

I’m trying to figure out the exact rules for Single-Abstract-Method (SAM) conversion in kotlin.

Here’s the example from the Try Kotlin Page:

fun getList(): List<Int> {
    val arrayList = arrayListOf(1, 5, 2)
    Collections.sort(arrayList, { x, y -> y - x })
    return arrayList
}

This compiles fine, as it should. Now, if we pull out the function argument into a variable:

fun getList(): List<Int> {
    val arrayList = arrayListOf(1, 5, 2)
    val compare: (Int, Int) -> Int =  { x, y -> y - x }
    Collections.sort(arrayList, compare)
    return arrayList
}

… it still compiles, as expected.

Now, consider the following example:

fun thisDoesNotCompile(){
    val handlers = mutableListOf<java.util.function.Consumer<String>>()
    val handler: (String)->Unit = {s: String ->println(s)}
    handlers.add(handler) // <- compile error here, "handler" has wrong type
}

I can’t really spot the difference to the previous example. (String)->Unit should be equivalent to Consumer<String> in terms of SAM conversion, yet the compiler complains that:

Required:
Consumer<String>
Found:
(String) -> Unit

Can anybody explain to me what happens here, specifically: why does the call to Collections.sort(...) compile with SAM conversion, but the call to List#add(...) does not? In Java, both are fine and it puzzles me that the kotlin compiler disagrees here.


#2

(String) -> Unit in Kotlin isn’t a Consumer<String>. Internally it’s a KFunction1<String, Unit> because Kotlin for soem reason doesn’t use the standard Java types but instead invents their own functional interfaces.

The sort call works because Kotlin does SAM conversion when calling into Java code. But the last code block doesn’t because your handlers list isn’t Java code.


#3

Oh, so thats the difference - the called code is written in Java. That explains it.

It’s not a very satisfying situation though; isn’t this exactly what SAM conversion is for? Will this stay like that? I heard somewhere that the Kotlin developers are working on improving SAM conversion capabilities.


#4

I think they are trying to improve SAM conversions. But in real Kotlin you woudn’t write

What you usually would write is

val handlers = mutableListOf<(String)->Unit>()

(and that get’s you internally a List<KFunction<String, Unit>> or something like that where you can add a normal lambda again.)


#5

Actually Java fails to really standardize function types. Yes there are types like Consumer, but there are also types with the same signature that don’t inherit from it.


#6

I think the way Java handles SAM conversion is actually pretty decent and user-friendly. It’s one of the few things I wouldn’t mind if Kotlin simply copied/mimicked the java way of doing things. Coming from Java, Kotlin can feel very restrictive (even needlessly restrictive?) when it comes to assigning Lambdas to something else.


#7

Same. Another benefit of the specific functional interfaces Java has is that you can define contracts. For Example Comparator<Int> has much clearer semantics than Kotlin (Int, Int)->Int which could as well be the multiply-function or whatever.