run

Next up on our tour of the standard functions is run. run is similar to apply in that it provides the same relative scoping behavior. However, unlike apply, run does not return the receiver.

Say you wanted to check whether a file contains a particular string:

     val menuFile = File("menu-file.txt")
     val servesDragonsBreath = menuFile.run {
         readText().contains("Dragon's Breath")
     }

The readText function is implicitly performed on the receiver – the File instance. This is just like the setReadable, setWriteable, and setExecutable functions you saw with apply. However, unlike apply, run returns the lambda result – here, a true or false value.

run can also be used to execute a function reference on a receiver. You used function references in Chapter 5; here is an example that shows their use with run:

    fun nameIsLong(name: String) = name.length >= 20

    "Madrigal".run(::nameIsLong)  // False
    "Polarcubis, Supreme Master of NyetHack".run(::nameIsLong) // True

While code like this is equivalent to nameIsLong(“Madrigal”), the benefits of using run become clear when there are multiple function calls: Chained calls using run are easier to read and follow than nested function calls. For example, consider the following code that checks whether a player’s name is 10 characters or longer, formats a message depending on the result, and prints the result.

    fun nameIsLong(name: String) = name.length >= 20
    fun playerCreateMessage(nameTooLong: Boolean): String {
        return if (nameTooLong) {
            "Name is too long. Please choose another name."
        } else {
            "Welcome, adventurer"
        }
    }

    "Polarcubis, Supreme Master of NyetHack"
        .run(::nameIsLong)
        .run(::playerCreateMessage)
        .run(::println)

Compare the calls chained with run to calling the three functions using nested syntax:

    println(playerCreateMessage(nameIsLong("Polarcubis, Supreme Master of NyetHack")))

The nested function calls are more difficult to understand because they require the reader to work from the inside out, rather than the more familiar top to bottom.

Note that there is a second flavor of run that is not called on a receiver. This form is far less commonly seen, but we include it here for completeness:

    val status = run {
        if (healthPoints == 100) "perfect health" else "has injuries"
    }