Reputation: 4502
Kotlin and Groovy both provide a way to write a high-order function where the function parameter has an implicit receiver.
Kotlin Version
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 Version
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() }
My goal is to write the withReceiver
function in Kotlin, but call it from groovy and have { hello() }
work. As written, though, Kotlin generates bytecode like
public final void withReceiver(@NotNull Function1 fn) { /* ... */ }
which Groovy treats as a function with a parameter. In other words, to call Kotlin's withReceiver
from Groovy, I have to do this:
(new KotlinVersion()).withReceiver { it -> it.hello() }
In order to allow { hello() }
with no it -> it.
, I have to add an overload that takes a groovy.lang.Closure
as its parameter.
Kotlin Version
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()
}
}
With that overload in place, given a KotlinVersion
instance called foo
the following line works in both languages:
// 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() }
I'm trying to keep that syntax, but avoid having to write that extra boilerplate overload for each high-order function my Kotlin library defines. Is there a better (more seamless/automatic) way of making Kotlin's function-with-receiver syntax usable from Groovy so I don't have to manually add a boilerplate overload to each of my Kotlin functions?
The complete code and compile instructions for my toy example above are on gitlab.
Upvotes: 6
Views: 1601
Reputation: 28564
in groovy you can define new functions dynamically
KotlinVersion.metaClass.withReceiver = { Closure c->
delegate.with(c)
}
this will define new function withReceiver
for class KotlinVersion
and will allow to use this syntax to KotlinVersion
instance:
kv.withReceiver{ toString() }
in this case toString()
will be called on kv
you can write the function that iterates through declared methods of your kotlin class with kotlin.Function
parameter and declare new method but with groovy.lang.Closure
parameter through metaClass.
Upvotes: 2