ntos
ntos

Reputation: 319

extension lambdas accept wrong parameters

I think an extension lambda requires that you pass in the correct argument, but i seems not to do so in the following example.

 

        open class Base {
        open fun f() = 1
    }
    class Derived : Base() {
        override fun f() = 99
    }
    fun Base.g(): Int { return f()}
    fun Base.h(xl: Base.() -> Int): Int { return xl()}
    
    fun main() {
        val b: Base = Derived() // Upcast
        println(b.g())
        println(b.h { f()}) // [1]
}

I understand that Base.h takes a function that takes a Base object as its parameter. But line [1] shows that it accepts f(), which is a function that takes no parameter. I was thinking hard about this and I prefixed it with this.f() and it still worked. Not convinced, I modified the code as follows:


    open class Base {
        open fun f() = 1
    }
    class Derived : Base() {
        override fun f() = 99
    }
    fun Base.g(): Int { return f()}
    fun Base.h(xl: (Base) -> Int): Int { return xl(Base())}
    fun test(i:Int) = 1
    fun main() {
        val b: Base = Derived() // Upcast
        println(b.g())
        println(b.h { test(1) })
    }

This code works. I've run it to verify. And as you can see, b.h() accepts test(), which takes an Int. And this is contrary to the fact that Base.h() takes a Base.

Could you explain this? Thank you for reading.

Upvotes: 1

Views: 68

Answers (1)

Sweeper
Sweeper

Reputation: 271420

Note the curly brackets around the functions that are passed in! They change everything.

In the second code, b.h { test(1) } is not passing the function test to b.h. The syntax to pass test to b.h would be b.h(::test), and that does produce an error as you would expect.

b.h { test(1) } passes a function (a lambda expression) that takes a Base as parameter, ignores that parameter, calls test(1) and returns the result. You are basically passing a function that looks like this to b.h:

fun foo(p: Base) = test(1)

You might be wondering how Kotlin knows about Base when you did not write the word Base in the call at all. Well, it can just look at the declaration of b.h, and see that { test(1) } must take a parameter of Base.

The first code snippet is a bit different, because b.h accepts a Base.() -> Int in that case. Base.() -> Int represents a function whose receiver type is Base, that is, a function func that can be called like someBaseObject.func(). Compare this to a function func that takes a Base object as parameter, which can be called like func(someBaseObject).

Again, { f() } is not passing the function f. It is a lambda expression that does nothing but calls f. In this case though, f itself can be passed to b.h (b.h(Base::f)), because it is a function with a receiver type of Base! You can do someBaseObject.f(), can't you? Passing the lambda is similar to passing an extension function that is declared like this (you're just "wrapping" f in another function):

fun Base.foo() = f()

And since the receiver of the function is Base, you are able to access other functions that has Base as the receiver (such as f) in the lambda. You can also specify the receiver (which is this) explicitly.

Upvotes: 2

Related Questions