Compile error when pass lambda functions


#1

Hello,
I got a compile error when passing lambda function as parameters. But when I use local val to store it’s reference. Then compile error just disappeared. Can someone help me figure out what happened here?

class Card {
    fun pay(i: Int) { println(i) }
}

class Main {

    val c = Card()

    fun badCall(f: Card.() -> Unit) { // badCall(Lkotlin/jvm/functions/Function1;)V
        c.f()
    }

    fun goodCall(f: (Card) -> Unit) { // goodCall(Lkotlin/jvm/functions/Function1;)V
        f.invoke(c)
    }
}

fun main(args : Array<String>) {
  val f = Card::pay
  val m = Main()

  m.badCall { x: Card -> f.invoke(x, 10)}  // Compile error !! : "Expected no parameters"
  val method  = { x: Card -> f.invoke(x, 10)}  // store it to local val
  m.badCall(method)                        // it compiles now

  m.goodCall { x: Card -> f.invoke(x, 10)} // it's okay for both case
  m.goodCall(method)
}

The error message is : “Error:(23, 17) Kotlin: Expected no parameters”
Byte code of badCall and goodCall looks exactly the same. But badCall won’t compile if I pass lambda function directly to it.
(I do some simple search on forum, but I didn’t find related topics. If the problem had been answered, please tell me. Thansk!)


#2

Your badCall() function takes an extension lambda. Extension lambda’s are compiled to take the receiver as the first parameter.

class Card {
    fun pay(i: Int) { println(i) }
}

class Main {

    val c = Card()

    fun badCall(f: Card.() -> Unit) { // badCall(Lkotlin/jvm/functions/Function1;)V
        c.f()
    }

    fun goodCall(f: (Card) -> Unit) { // goodCall(Lkotlin/jvm/functions/Function1;)V
        f.invoke(c)
    }
}

fun main(args : Array<String>) {
  val f = Card::pay
  val m = Main()

    //sampleStart
  //m.badCall { x: Card -> f.invoke(x, 10)}
  m.badCall { f.invoke(this, 10)}  //sampleEnd
  // m.badCall { x: Card -> f.invoke(x, 10)}
  val method  = { x: Card -> f.invoke(x, 10)}  // store it to local val
  m.badCall(method)                        // it compiles now

  m.goodCall { x: Card -> f.invoke(x, 10)} // it's okay for both case
  m.goodCall(method)
}

As for why it works when passing in the function reference and not when providing a receiver as the first parameter, I suspect it is either,

  1. a bug
  2. the intended behavior since it is an extension lambda and you’re passing in a lambda with a param
  3. a bug that should not let you use m.badCall(method) since you’re passing in a lambda with a param when there is no explicit param

My personal pick is #2, the intended behavior.

Try renaming badCall and goodCall to the same name and notice how the method resolution changes. There would no longer be a compiler error on m.badCall { x: Card -> f.invoke(x, 10)}, but instead there would be a compiler error on m.goodCall(method) for ambiguity.


#3

Thank you very much, it’s very helpful :smiley: