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!