9

According to the following source code it seems that regular lambdas are interchangeable with extension lambdas.

fun main(args: Array<String>) {

    val numbers = listOf(1, 2, 3)

    filter(numbers, predicate)
    filter(numbers, otherPredicate)

    println("PREDICATE: ${predicate} " +
        "\nOTHERPREDICATE: ${otherPredicate} " +
        "\nEQUALITY: ${predicate==otherPredicate}")
}

val predicate : Int.() -> Boolean = {this % 2 != 0}
val otherPredicate : (Int) -> Boolean = {it % 2 != 0}


fun filter(list: List<Int>, predicate:(Int) -> Boolean) {
    for(number in list){
        if(predicate(number)){
            println(number)
        }
    }
}

The output (I care about), is the following:

PREDICATE: kotlin.Int.() -> kotlin.Boolean 
OTHERPREDICATE: (kotlin.Int) -> kotlin.Boolean 
EQUALITY: false

The question is why are these lambdas interchangeable? Shouldn't be something different? Is the compiler doing something "smart" under the hood?

s1m0nw1
  • 76,759
  • 17
  • 167
  • 196
LiTTle
  • 1,811
  • 1
  • 20
  • 37

1 Answers1

6

Differences

It's not entirely interchangeable since "extension lambdas", technically called lambdas with receiver, can be invoked on a receiver, which is not possible for regular lambdas:

predicateWithReceiver(2) //OK
2.predicateWithReceiver() //OK

regularPredicate(2) //OK
2.regularPredicate //Not OK

Lambdas with receiver may be invoked as ordinary functions with an argument but can also be invoked on their receiver object directly (similar to extensions). The more important part is how such lambdas look like on the caller site, i.e. you do not need to use qualifiers to access visible members of that receiver inside such a lambda.

Compilation

This is enabled by compiler techniques. The following demonstrates how 2.regularPredicate looks like on bytecode level (shown as decompiled Java):

  Function1 predicateWithReceiver = ...;
  predicateWithReceiver.invoke(2);

It looks like a regular function call, the translation is taken care of by the compiler.

EDIT

As for a higher-order function such as filter it doesn't really make a difference. See how it is compiled (again depicted as Java):

public static final void filter(@NotNull List list, @NotNull Function1 predicate) {

  //...
     if ((Boolean)predicate.invoke(number)) {
        System.out.println(number);
     }
  }

}

The filter function takes an instance of Function1. Both, regular and lambdas with reiceiver are compiled to exactly such an object. As a result, it doesn't make a difference how you define the argument predicate in your Kotlin code.

s1m0nw1
  • 76,759
  • 17
  • 167
  • 196
  • Even if I change the filter function to this: fun filter(list: List, predicate:Int.() -> Boolean):Unit{ for(number in list){ if(number.predicate()){ println(number) } } } It is still working. – LiTTle Feb 01 '18 at 09:15
  • 1
    @LiTTle see the EDIT section. – s1m0nw1 Feb 01 '18 at 09:35
  • The beauty of lambda with receiver is that in the lambda's body you get an implicit `this`, pointing to receiver object. Hence you can use it to access the receiver object and its members directly, without any additional qualifiers like `it`. – dbzix Nov 16 '21 at 18:25