Learning Kotlin: Invoke

Submitted by Robert MacLean on Thu, 08/16/2018 - 09:00
**More Information** * This is the 21st post in a multipart series. If you want to read more, see our [series index](/learning-kotlin-introduction)

Today we tackle a weird operator, invoke which lets an instance of a class have a default function - which I am not sure I've ever seen any language do. So let us frame this with a simple example, we have a config class which returns the configuarion for something:

  1. class Config {
  2.     fun get():String {
  3.         // do stuff
  4.         return "stuff"
  5.     }
  6. }
  7.  
  8. fun main(args: Array<String>) {
  9.     val config = Config()
  10.     println(config.get())
  11. }

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

  1. class Config {
  2.     operator fun invoke(): String {
  3.         return this.get();
  4.     }
  5.  
  6.     private fun get():String {
  7.         // do stuff
  8.         return "stuff"
  9.     }
  10. }
  11.  
  12. fun main(args: Array<String>) {
  13.     val config = Config()
  14.     println(config())
  15. }

Note that we add a new operator (line 2), and that calls the private get; it didn't need to be private but I thought let us have this be cleaner, and now on line 14 we can just call the instance itself.

Now, you may be thinking... nice but so what saving a few keystrokes isn't too awesome. Well, invoke can return anything, including itself which opens up something crazy.

  1. class Config {
  2.     var count = 0;
  3.     operator fun invoke(): Config {
  4.         count++
  5.         return this
  6.     }
  7. }
  8.  
  9. fun main(args: Array<String>) {
  10.     val config = Config()
  11.     config()()()()()()()()()()
  12.     println("config was called ${config.count} times")
  13. }

This will print out config was called 10 times. That is getting more interesting, so let us ramp up another level and pass parameters to invoke:

  1. class Config {
  2.     var word = ""
  3.     operator fun invoke(s: String): Config {
  4.         word += s
  5.         return this
  6.     }
  7. }
  8.  
  9. fun main(args: Array<String>) {
  10.     val config = Config()
  11.     config("R")("o")("b")("e")("r")("t")
  12.     println(config.word)
  13. }

While I do not know yet where I would use this myself, I do use invoke all the time... since it is what makes lambdas possible in Kotlin as when we create a lambda we get an object which is invoked with well... invoke.