问题描述
最近我决定进入 Kotlin 并编写了一个玩具应用程序。我想使用 Kotlin 脚本接口扩展我的应用程序。
从中收集到的信息,我需要使用 6
,这似乎也有效。我能够执行这样的脚本:
javax.script.ScriptEngine
这就像一个魅力。但是,我想进一步改进我的界面并省略 robot.configure {
name = "FrankaPanda"
}
调用。基本上,我想实现以下脚本:
configure
为了实现这一点,我认为“机器人”需要是一个接受 lambda 的函数。 但是,每当我尝试将方法绑定到 ScriptEngine
robot {
name = "FrankaPanda"
}
我的脚本评估失败,出现异常:
javax.script.ScriptException:未解析的引用:机器人
我也尝试了其他几种方法,包括 val robot = Robot()
scriptEngine.put("robot",robot::configure)
等,但都失败了。但是,我真的想省略显式调用 configure 并使我的界面更“Gradle-esque”。
问题:
- 如何使用 Kotlin 创建 Gradle 风格的脚本界面?
- (奖励)Gradle 如何将任意“扩展”转换为接受 lambda 的函数?
解决方法
好吧,我希望有更优雅的解决方案,但我找不到。
第一件事: 您不需要传递函数。一个可调用的类就足够了。当一个实例的类实现了操作符“invoke”时,它是可调用的。例如:
open class Extension {
operator fun invoke() { /* ... */ }
}
要通过(脚本)lambda 配置扩展,您需要扩展 invoke 函数以接受 lambda:
open class Extension {
operator fun invoke(init: Extension.() -> Unit) {
init(this)
}
}
但是,这个问题的前提是扩展本身没有实现操作符。相反,我想添加它们。定义扩展方法是行不通的,因为它受范围的限制。
这可以通过在脚本中定义扩展方法来避免:
type: Class<T>;
val code = """
operator fun ${type.name}.invoke(init: ${type.name}.() -> Unit) {
init(this)
}
""".trimIndent()
scriptEngine.eval(code)