6

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.

Dan
  • 4,312
  • 16
  • 28
  • where is the limit? why you can't create kotlin code that in byte code will look like this: `public final void withKotlinReceiver(groovy.lang.Closure fn) { /* ... */ }` ? – daggett Jul 02 '18 at 22:44
  • @daggett The Kotlin example in my workaround does exactly that, I'm just trying to avoid having to write a wrapper like that by hand for every high-order function in my library. I thought there might be some combination of Groovy magic and/or Kotlin compiler attributes that could achieve the same syntax without tons of hand-written boilerplate. – Dan Jul 04 '18 at 00:59
  • Please provide more details how do you see the groovy script/class and how it imports kotlin.. – daggett Jul 04 '18 at 08:49
  • I'm not sure what you're asking, so I've edited the question to try to make it clearer what I'm trying to achieve. The context is that my Kotlin code is in a `buildSrc` folder, and I'm trying to make it callable from gradle scripts written in Groovy. I've checked the toy example, with compile instructions, [into gitlab](https://gitlab.com/drmoose/so-how-can-i-call-with-receiver-from-groovy) – Dan Jul 04 '18 at 14:46

1 Answers1

2

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.

daggett
  • 26,404
  • 3
  • 40
  • 56