Kotlin函数参数接收器,从Groovy调用

6
Kotlin和Groovy都提供了一种编写高阶函数的方式,其中函数参数具有隐式接收者。 Kotlin版本
class KotlinReceiver { 
    fun hello() { 
        println("Hello from Kotlin") 
    } 
}

class KotlinVersion {
    fun withReceiver(fn: KotlinReceiver.() -> Unit) {
        KotlinReceiver().fn() 
    } 
}

// And then I can call...
val foo = KotlinVersion()
foo.withReceiver { hello() }

Groovy 版本

class GroovyReceiver { 
    void hello() { 
        println("Hello from Groovy") 
    } 
}

class GroovyVersion {
    void withReceiver(Closure fn) {
        fn.resolveStrategy = Closure.DELEGATE_FIRST
        fn.delegate = new GroovyReceiver()
        fn.run()
    }
}

// And then I can call...
def foo = new GroovyVersion()
foo.withReceiver { hello() }

我的目标是在Kotlin中编写withReceiver函数,但从Groovy调用它并使{ hello() }正常工作。然而,如此编写的话,Kotlin会生成类似下面的字节码:

public final void withReceiver(@NotNull Function1 fn) { /* ... */ }

Groovy将其视为带有参数的函数。换句话说,要从Groovy调用Kotlin的withReceiver,我必须这样做:
(new KotlinVersion()).withReceiver { it -> it.hello() }

为了让 { hello() } 不需要 it -> it.,我需要添加一个重载函数,该函数以 groovy.lang.Closure 作为参数。 Kotlin 版本
import groovy.lang.Closure

class KotlinVersion { 
    fun withReceiver(fn: KotlinReceiver.() -> Unit) {
         KotlinReceiver().fn()
    }

    fun withReceiver(fn: Closure<Any>) = withReceiver {
        fn.delegate = this
        fn.resolveStrategy = Closure.DELEGATE_FIRST
        fn.run()
    }
}

有了这种超载,给定一个名为fooKotlinVersion实例,以下行在两种语言中都可以正常工作:

// If this line appears in Groovy code, it calls the Closure overload.
// If it's in Kotlin, it calls the KotlinReceiver.() -> Unit overload.
foo.withReceiver { hello() }

我想保留这个语法,但避免为我的Kotlin库定义的每个高阶函数编写额外的样板重载。有没有更好(更无缝/自动化)的方法可以让Groovy使用Kotlin的带接收者函数语法,以便我不必手动为每个Kotlin函数添加样板重载?我的玩具示例的完整代码和编译说明在gitlab上

限制在哪里?为什么你不能创建 Kotlin 代码,使其字节码看起来像这样:public final void withKotlinReceiver(groovy.lang.Closure fn) { /* ... */ } - daggett
@daggett 我的解决方法中的 Kotlin 示例正是这样做的,我只是试图避免为我的库中的每个高阶函数手动编写这样的包装器。我认为可能有一些 Groovy 魔法和/或 Kotlin 编译器属性的组合可以实现相同的语法,而不需要大量手写的样板代码。 - Dan
请提供更多细节,说明您如何查看Groovy脚本/类以及它如何导入Kotlin。 - daggett
我不确定你在问什么,所以我编辑了问题,试图让我的目标更清晰。背景是我的 Kotlin 代码在 buildSrc 文件夹中,我正在尝试使其可从用 Groovy 编写的 gradle 脚本中调用。我已经检查了玩具示例,并提供了编译说明,放到了 GitLab 上 - Dan
1个回答

2
在Groovy中,您可以动态定义新函数。
KotlinVersion.metaClass.withReceiver = { Closure c-> 
    delegate.with(c) 
}

这将为 KotlinVersion 类定义新的函数 withReceiver,并允许使用以下语法访问 KotlinVersion 实例:
kv.withReceiver{ toString() }

在这种情况下,toString()将在kv上调用。
您可以编写一个函数,使用kotlin.Function参数迭代您的Kotlin类的声明方法,并通过metaClass声明新方法,但使用groovy.lang.Closure参数。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接