Dan
Dan

Reputation: 4502

Kotlin function parameter with receiver, called from Groovy

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

Answers (1)

daggett
daggett

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

Related Questions