NativeScript / android

NativeScript for Android using v8
https://docs.nativescript.org/guide/android-marshalling
Apache License 2.0
519 stars 136 forks source link

Support Kotlin higher-order functions/lambdas #977

Open jsommr opened 6 years ago

jsommr commented 6 years ago

NativeScript supports marshalling Java bytecode generated by Kotlin. But Kotlin has a way to specify that a function takes another function in a much better way than Java.

Like this:

fun myfun(callback: (arg: String) -> Void)

It compiles to something like

public final void myfun(@org.jetbrains.annotations.NotNull final kotlin.jvm.functions.Function1<? super String, Void> callback)

where Function1 is a class containing an invoke method. Depending on how many args the function takes, it'll be called Function2, Function3 and so on.

Interacting with myfun from JavaScript would be done this way:

myfun(new kotlin.jvm.functions.Function1({ invoke: () => console.log("invoked") }))

It would be so cool if NativeScript supported Kotlin-functions out of the box, so the callback wouldn't need to be wrapped in FunctionX and myfun could be run via myfun(() => console.log("invoked")). This would make it easier to align with an Objective-C library, where blocks are supported.

To support Kotlin, my app.gradle currently looks like this:

buildscript {
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.3.3'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.2.30"
    }
}

dependencies {
  compile fileTree(dir: "libs", include: ["*.jar"])
  compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:1.2.30"
}

android {  
  defaultConfig {  
    generatedDensities = []
    applicationId = "org.nativescript.test"  
  }  
  aaptOptions {  
    additionalParameters "--no-version-vectors"  
  }  
} 
jsommr commented 6 years ago

If someone can give me a few hints about where to look, perhaps I can make the appropriate changes myself :)

petekanev commented 6 years ago

@nerfpops I don't find this feature to have a straightforward implementation. Here's why:

You'd have to generate additional metadata for the generated anonymous functions. Next step is figuring out how to wire the lambda callback to the equivalent of a fat arrow function declaration, and fat arrow function declaration to a kotlin lambda. Are there cases where javascript fat arrow functions should not be treated as kotlin lambdas? Should named functions be allowed as parameters? Will there be a problem if the functor (myFunc) expects a callback with a number of parameters, but those are not handled in the javascript function? Is it possible that the functor has many overloads of different callback signatures?

jsommr commented 6 years ago

@Pip3r4o Not a Kotlin-expert in any way but I'll try to answer your questions

Are there cases where javascript fat arrow functions should not be treated as kotlin lambdas? According to the documentation lambda expressions supports the following syntax:

class HTML {
    fun body() { ... }
}

fun html(init: HTML.() -> Unit): HTML {
    val html = HTML()  // create the receiver object
    html.init()        // pass the receiver object to the lambda
    return html
}

// Example usage
html {       // lambda with receiver begins here
    body()   // calling a method on the receiver object
}

Using html from javascript the same way as the example above, would look like

html(function() {
    // `this` is now the class returned from html
    this.body()
})

// or

html(context => {
    context.body()
})

I'm sure this wouldn't be easy to implement. Could it be something that's unsupported in the start?

Are there cases where javascript fat arrow functions should not be treated as kotlin lambdas? The only method on FunctionX is invoke. (arg1, arg2, ...) => { code } would simply be a shorthand for new kotlin.jvm.functions.FunctionX({ invoke: (arg1, arg2, ...) => { code } })

Should named functions be allowed as parameters? Sure. I don't see why not, but I'm also not aware of the difference between passing an anonymous and named function. Or the complexity in passing a function from javascript to java.

Will there be a problem if the functor (myFunc) expects a callback with a number of parameters, but those are not handled in the javascript function? It's important to match the FunctionX signature in Kotlin, eg.

fun myfun(callback: (arg: String) -> Void)

called from javascript via

// Don't care about the string in the callback
myfun(() => console.log("invoked"))

would have to be turned into something like

new kotlin.jvm.functions.Function1({ invoke: () => { code } })

where Function1 is important, even though the javascript callback doesn't care about the returned string.

Is it possible that the functor has many overloads of different callback signatures? Kotlin doesn't support union types and doesn't support dynamic callback signatures. It needs to explicitly know what is being passed and how many arguments are required. It's 24 classes, from Function0-23, with an invoke-method.