Help understanding functional literals with receivers

I really felt like I was grasping the concept of lambdas. That is until I got this example of a functional literal with a receiver (from Kotlin Programming: The Big Nerd Ranch guide).

Here’s the code I’m confused about:

fun Room.configurePitGoblin(block: Room.(Goblin)->Goblin): Room {
    val goblin = block(Goblin("Pit Goblin", "An Evil Pit Goblin"))
    monster = goblin
    return this
}

fun main() {
    var currentRoom: Room = Room("Town Square")
    currentRoom.configurePitGoblin { goblin -> goblin.healthPoints = dangerLevel * 3
        goblin }
}

Then, if you need to see the details for the classes, here they are:

Room and Goblin class definitions:
open class Room (val name: String) {
    open val dangerLevel = 5

    var monster : Monster? = Goblin()

    fun description() = "Room: $name\n"+
            "Danger level: $dangerLevel\n" +
            "Creature: ${monster?.description ?: "none"}"
}

interface Fightable {
    var healthPoints: Int
    val diceCount: Int
    val diceSides: Int
    val damageRoll: Int
        get() = (0 until diceCount).map {
            Random().nextInt(diceSides)+1
        }.sum()

    fun attack(opponent: Fightable): Int
}

abstract class Monster (val name: String, val description: String, override var healthPoints: Int) : Fightable {
    override fun attack(opponent: Fightable): Int {
        val damageDealt = damageRoll
        opponent.healthPoints -= damageDealt
        return damageDealt
    }
}

class Goblin(name: String = "Goblin", description: String = "A nasty-looking goblin", healthPoints: Int = 30) : Monster(name, description, healthPoints) {
    override val diceCount = 2
    override val diceSides = 8
}

So, I’m gonna explain my understanding of lambdas using some different code, borrowed from Kotlin Programming: The Big Nerd Ranch guide:

fun main() {
    runSimulation("Guyal") {playerName, numBuildings ->
        val currentYear = 2018
        println("Adding $numBuildings houses")
        "Welcome to SimVillage, $playerName! (copyright $currentYear)"
    }
}

fun runSimulation(playerName: String, greetingFunction: (String, Int)->String) {
    val numBuildings = (1..3).shuffled().last()
    println(greetingFunction(playerName, numBuildings))
}

This block of code is easy for me to understand. We have a function (runSimulation) that accepts 2 parameters: one is a string, and one is a lambda. When the function is called in main, 2 arguments are passed, one is a string, and the other is a lambda. The function definition is requesting a lambda of type (String, Int)->String, and the function call fulfills this requirement.


So what is happening in the FIRST block of code I posted, when the function in question is an extension?
It almost seems like things are happening in reverse.
The configurePitGoblin function is called on a Room object, currentRoom. A lambda is passed, sure.

But now we run the ‘configurePitGoblin’ function and the lambda that was passed is “stored” in a variable called ‘block’, but it is supposed to be of type Room.(Goblin)->Goblin. It seems to me that the lambda that was passed does NOT contain any information about a Goblin object. Rather, the Goblin is first instantiated here in this function within the block. How can this function expect to receive a Goblin object that it itself is creating? In the extension function call, we are telling the compiler to do stuff to a Goblin object that doesn’t exist yet. None of this makes sense to me.

Then the lambda is expected to return an object of type Goblin. I guess it does indeed do this when monster (aka ‘this.monster’), which is a property of Room, is assigned to the instantiated Goblin object. However, this isn’t the last statement of the lambda; return this is. How does Kotlin know that monster = goblin is the return statement for the lambda, and ‘return this’ is the return statement of the function?

Thanks for any help!

All data passed to a method call as parameter is calculated / created before the method is called.

So, what this line actually does:

val goblin = block(Goblin("Pit Goblin", "An Evil Pit Goblin"))

  1. create a Goblin object instance
  2. call the block lambda with the created object instance passed as parameter
  3. set the value of the goblin variable to the value returned by block, the goblin instance in this case

The goblin object instance is actually created in configurePitGoblin before it calls block.

The last statement of the lambda block is goblin.
The last statement of configurePitGoblin is return this.

Hope this helps.

Edit: One more thing. This example is not really good because it makes confusion in names. To make things more clear:

fun Room.configurePitGoblin(block: Room.(Goblin)->Goblin): Room {
    val unnecessary_variable = block(Goblin("Pit Goblin", "An Evil Pit Goblin"))
    monster = unnecessary_variable
    return this
}

fun main() {
    var currentRoom: Room = Room("Town Square")
    currentRoom.configurePitGoblin { parameter_of_block_lambda ->
        parameter_of_block_lambda.healthPoints = dangerLevel * 3 // dangerLevel is from Room
        return parameter_of_block_lambda 
    }
}

That is unintuitive, isn’t it? I mean, it’s great that it works that way, but I would have never thought to code like that.
Still, how does the compiler know, and more importantly, how am I supposed to know what “type” to give a lambda that is passed to a function that is going to create it’s own objects in it’s own lambda? Since no Goblin object is actually passed in the currentRoom.configurePitGoblin call, I’m having a hard time NOT seeing the type of that lambda as ()->Unit or MAYBE ()->Goblin.

How exactly is this accomplished? Would this look something like:

val goblin = Goblin("Pit Goblin", "An Evil Pit Goblin")
val block: (Goblin)->Goblin = {parameter_of_block_lambda -> parameter_of_block_lambda.healthPoints = dangerLevel * 3}

val unnecessary_variable: Goblin = block(goblin)
monster = unnecessary_variable

Obviously, not THIS code exactly, since the original code is an extension function, and this code excerpt isn’t, but is the underlying concept of assigning a lambda to block and passing the newly created Goblin object into that block lambda the same?

The Lambda doesn’t create the object. The extension function does.

Your are on the right path your code fragment, that is what happens.

I think the best is to forget the world “lambda”. It is confusing and unnecessary. Better to say “anonymous function”. It is just a function and some nice syntax to use it.

Your lambda can be rewritten like this:

fun Room.gobliner(goblin : Goblin) : Goblin {
    goblin.healthPoints = dangerLevel * 3
    return goblin
}

fun Room.configurePitGoblin(block : (Goblin) -> Goblin) {
    monster = block(Goblin("Pit Goblin"), "An Evil Pit Goblin")
}

fun main() {
    var currentRoom: Room = Room("Town Square")
    currentRoom.configurePitGoblin(currentRoom::gobliner)
}

The type to pass to the lambda comes from the signature of configurePitGoblin. It states that:

  1. block is a parameter of type function
  2. that function has one parameter of type Goblin
  3. that function returns with a Goblin
fun Room.configurePitGoblin(block : (Goblin) -> Goblin) {
2 Likes

Unfortunately that’s not good advice. Lambdas and anonymous functions are different things in Kotlin.

Let’s step back a bit and try to explain this with something simpler.

fun foo(message: String) {...}
fun bar(): String {...}
foo(bar())

So foo() is a function that takes a string. When you call foo(), first the argument is evaluated to determine exactly what value it has. If the argument is some literal value, like “hello” (e.g. foo("hello")) then this is simple. But the argument can be any expression. In this case it is a call to the function bar(), which returns a string. So whatever string bar() returns is passed as the argument to foo(). bar() is called before foo() is called, outside of foo().

Let’s look at your example. The configurePitGoblin function has a parameter named block, which contains a lambda. The syntax block(...) means “execute the lambda contained in the parameter block, passing in the following argument(s).” So first it needs to evaluate the argument, which is done by instantiating a new Goblin object. This Goblin object is then passed to block as its argument. So when you say, “the Goblin is first instantiated here in this function,” that is correct, but when you say, "within the block", that is incorrect. The Goblin is instantiated in the function and then passed to the block.

OK, now that we’ve established that the Goblin isn’t created inside the lambda, it should make more sense what the type of the block parameter is. It is a lambda which takes a parameter of type Goblin and returns a Goblin. And if you look at the main() function, where it calls configurePitGoblin(), it does indeed pass in a lambda that takes a Goblin and returns a Goblin.

Of course that’s ignoring the lambda’s receiver. The actual type of the block parameter is Room.(Goblin)->Goblin), which means that when the lambda is executed there will be a this parameter of type Room. So the lambda you pass into configurePitGoblin can access any value of currentRoom (such as dangerLevel) through this.

2 Likes

Thanks guys, I do understand it better now! I appreciate the responses.

I don’t agree that they are different things. The syntax is different. From the official documentation (emphasis by me).

One thing missing from the lambda expression syntax presented above is the ability to specify the return type of the function. In most cases, this is unnecessary because the return type can be inferred automatically. However, if you do need to specify it explicitly, you can use an alternative syntax: an anonymous function .

So, for me they are the same thing, I can use the alternative syntax when I want to specify the type exactly. Just like in this case:

val s : Any = "Hello"
val s = "Hello"

There is one important difference. return returns from the enclosing function. So if you have something like

fun foo() {
    callWithTrue( fun (b: Boolean) { 
        if(b) return 
        else println("in anonymous") 
    })
}
fun foo2() {
    callWithTrue { b:  Boolean -> 
        if(b) return 
        else println("in lambda") 
    }
}

inline fun callWithTrue(block: (Boolean)->Unit) {
    println("before call")
    block(true)
    println("after call")
}

fun main() {
    foo()
    foo2()
}

You will get

before call
after call
before call

So lambdas and anonymous functions are different.

1 Like

Relevant Kotlin documentation: https://kotlinlang.org/docs/reference/returns.html#return-at-labels

What happens when you remove “inline”? That is just as important as having “inline”.

Yes, there is a case - which is basically a text macro - that makes it different.

But let’s do not confuse text macros (i.e. inline) with actual code.

However, you have a valid point, I admit.

On the other hand you should admit, that on conceptual level they are the same. :stuck_out_tongue:

Thinking about it a bit more, I think the documentation is misleading to call it an “alternative syntax”.
Because if we take the different return into account it is an “alternative mechanism”.

A lambda block that is given to a non-inline function cannot return from the outer function. It cannot have a (non-labeled) return statement at all (syntactically not allowed).

An anonymous function can have a non-labeled return statement. But it will never return from the outer function, but instead will return from the anonymous function itself.

Quite the contrary: on conceptual level they are different. The lambda has return transparency (it allows non-local returns), whereas the anonymous function does not.

In Kotlin you need inline functions to support non-local returns in lambda blocks. But that is just a technical limitation of the language. Theoretically non-local returns could also be supported without inline functions. But that would be very inefficient with the current means provided by the JVM. With inline functions the costs are literally zero.

You see, my point exactly: “that is just a technical limitation of the language”.

This is why I don’t like the distinction between lambdas and anonymous functions.

I understand that there is a difference and I understand why. But still, on conceptual level they are the same.

Let’s try to see this another way. Assume there were no technical limitations. Then lambda blocks would always support non-local returns, and anonymous functions would not. This is how they are designed to work. In my eyes that’s a conceptual difference.