Learning Kotlin: Invoke

More Information This is the 21st post in a multipart series. If you want to read more, see our series index

Today we tackle a weird operator, invoke, which lets an instance of a class have a default function—something I’m not sure I’ve ever seen any language do. Let’s frame this with a simple example: we have a Config class that returns configuration for something:

class Config {
    fun get(): String { // do stuff
        return "stuff"
    }
}

fun main(args: Array<String>) {
    val config = Config()
    println(config.get())
}

Now, in our world, maybe get is the primary use, so we can make it so that the instance config (line 9) can be called directly to get it:

class Config {
    operator fun invoke(): String {
        return this.get()
    }

    private fun get(): String { // do stuff
        return "stuff"
    }
}

fun main(args: Array<String>) {
    val config = Config()
    println(config())
}

Note: we add a new operator (line 2), and it calls the private get—though it didn’t need to be private, I thought this would be cleaner. Now, on line 14, we can just call the instance itself.

Now, you might be thinking… nice, but so what? Saving a few keystrokes isn’t that awesome. Well, invoke can return anything—even itself—opening up something truly powerful.

class Config {
    var count = 0

    operator fun invoke(): Config {
        count++
        return this
    }
}

fun main(args: Array<String>) {
    val config = Config()
    config()()()()()()()()()()
    println("config was called ${config.count} times")
}

This will print out: config was called 10 times

That’s getting more interesting, so let’s ramp up another level and pass parameters to invoke:

class Config {
    var word = ""

    operator fun invoke(s: String): Config {
        word += s
        return this
    }
}

fun main(args: Array<String>) {
    val config = Config()
    config("R")("o")("b")("e")("r")("t")
    println(config.word)
}

While I don’t yet know where I’d use this myself, I do use invoke all the time… since it’s what makes lambdas possible in Kotlin. When we create a lambda, we get an object that’s invoked with—well—invoke.