Search code examples
kotlininline-functions

Is it possible to pass inline functions in Kotlin?


When writing code for JavaScript with KotlinJs, we usually have to handle the this carefully.

So we usually need this function:

inline fun <T> thisAs() = js("this")

and use it in somewhere:

this.click = {
    thisAs<MyVueComponent.Data>().username = "changed"
}

Due to VueJs' inner logic, I have to generate JavaScript code this.username(this is hardcode), but which is ugly, I don't want to write thisAs<MyVueComponent.Data>().username every time I need a JavaScript this.

So I tried to improve it by defining a function defineMethods, like following:

external interface VueComponent<DATA, METHODS> {}

fun <DATA, METHODS> VueComponent<DATA, METHODS>.defineMethods(block: M.(() -> DATA) -> Unit): METHODS {
    val methods = jsObj<M>()
    block(methods, ::thisAs)
    return methods
}

And I hope the inline function thisAs is still a inline function even if I pass it to the block as ::thisAs, so I can rewrite the code like this:

this.methods = this.defineMethods { data ->
    this.click = {
        data().username += "changed!"  // I want to write `data()` to generate a JavaScript `this`
    }
}

But sadly, the function data(which is ::thisAs) is not inline anymore, the code is not working properly.

Is there any way to solve this?


Update:


Solution

  • I'm facing the same problem. I have a similar function for casting Javascript's this object as you do. However, I've extended it with a lambda function with a receiver (the same thing that is used for type-safe builders in Kotlin except that my function returns Unit and is inlined).

    inline fun <T> withJsThis(body: T.() -> Unit) {
      js("this").unsafeCast<T>().body()
    }
    

    It can be easily used in methods as well as in lifecycle hooks (created, mounted, etc.). It needs one extra line of code but it's clean and produce exactly the expected this.variable when translated from Kotlin to Javascript.

    class LoginComponent {
    
      class Data {
        var username: String = "[email protected]"
        var password: String = ""
      }
    
      val data = {
        Data()
      }
    
      val template = """<form>
          <input type="text" v-model="username" placeholder="Email" /><br />
          <input type="password" v-model="password" placeholder="Password" /><br />
          <input type="submit" v-on:click.prevent="showInfo" />
        </form>"""
    
      val methods = object {
    
        val showInfo: () -> Unit = {
          withJsThis<Data> {
              console.log("METHOD: USER = $username, PASS = $password")
          }
        }
    
      }
    
      fun mounted() {
        withJsThis<Data> {
          console.log("MOUNTED: USER = $username, PASS = $password")
        }
      }
    
    }