На этом шаге мы рассмотрим особенности использования таких функций.
Существует возможность использовать анонимные функции с приемниками, обладающие мощным эффектом. Чтобы понять, что имеется в виду под "анонимными функциями с приемниками", посмотрите на объявление функции apply(), которая встречалась вам на 99 шаге:
public inline fun <T> T.apply(block: T.() -> Unit): T { block() return this }
Напомним, что apply() позволила вам установить свойства конкретного экземпляра объекта-приемника внутри лямбды, которая передается в аргументе. Например:
val menuFile = File("menu-file.txt").apply { setReadable(true) setWritable(true) setExecutable(false) }
Это позволило избежать явного вызова каждой функции для переменной menuFile. Вместо этого вы вызвали их неявно, внутри лямбды. Волшебство apply() обеспечивается объявлением анонимной функции с объектом-приемником.
Еще раз взгляните на объявление apply() и обратите внимание, как объявлен параметр-функция с именем block():
public inline fun <T> T.apply(block: T.() -> Unit): T { block() return this }
Параметр-функция block() является не только лямбдой, но и расширением обобщенного типа T: T.() -> Unit. Именно это позволяет лямбде иметь неявный доступ к функциям и свойствам экземпляра приемника.
Приемник в лямбде, указанный в расширении, одновременно является экземпляром, для которого вызывается apply(), что открывает доступ к функциям и свойствам приемника в теле лямбды.
Подобный стиль позволяет создавать так называемый предметно-ориентированный язык, который можно назвать API-подобным. Он использует функции приемника, настроенные с использованием лямбда-выражений, объявленных для доступа к ним. Например, фреймворк Exposed от JetBrains (github.com/JetBrains/Exposed) широко использует стиль предметно-ориентированного языка для определения своего API, который помогает объявлять SQL-запросы.
Вы можете добавить в NyetHack функцию, которая будет использовать такой же стиль для настройки смертельно опасного гоблина в комнате.
fun Room.configurePitGoblin(block: Room.(Goblin) -> Goblin): Room { val goblin = block(Goblin("Pit Goblin", description = "An Evil Pit Goblin")) monster = goblin return this }
Это расширение для Room принимает лямбду, которая получает Room в роли приемника. В результате свойства Room будут доступны внутри вашей лямбды, поэтому гоблина можно настроить, используя свойства приемника Room:
currentRoom.configurePitGoblin { goblin -> goblin.healthPoints = dangerLevel * 3 goblin }
На следующем шаге мы рассмотрим несколько заданий для самостоятельного решения.