G* Advent Calendar 2017 の 6日目は groovy.lang.Closure
のインスタンスを Kotlin で生成・取得する方法です。
3分くらいで読み終われます。
groovy.lang.Closure
のインスタンスを Kotlin で生成・取得する方法です。Kotlin コードの中で Closure
を作って Groovy に渡すということを指しています。
生成方法
早速ですが生成方法です。
ある A
というクラスがあって、 Closure
の中で用いるプロパティを A
から取得したい場合は次のような関数を書きます。
fun <A> closure(a: A, cl: A.() -> Unit): Closure<Unit> = object: Closure<Unit>(a) { fun doCall() = this@closure.cl() }
ちょっと欲張りに A
というクラスで宣言した Closure
で、中で用いるプロパティを B
から取得しつつ、 C
を引数で受け取って、 D
を返す Closure
を生成したい場合は次のような関数を書きます。
fun <A, B, C, D> closure(declaredAt: A, receiver: B, cl: B.(C) -> D): Closure<D> = object: Closure<Unit>(this) { init { delegate = receiver } fun doCall(c: C): D = this@closure.cl(c) }
試してみる
最後の欲張りな記述方法を用いた例を書いてみます。次のような Kotlin コードを書きます。
object ClosureCreation { @JvmStatic fun <A> stringToIntClosure(declaredAt: A, delegate: Supplier<String>): Closure<Int> = closure<A, Supplier<String>, String, Int>(declaredAt, delegate) { s -> "${get()} $s".length } }
この stringToIntClosure
関数を呼ぶと java.util.function.Supplier
を引数にとって、kotlin.Int
(実質は int
) が返ってくる groovy.lang.Closure
が返ってきます。
そして、こちらが利用するコードになります。
class ClosureCreationTest { @Test void 'Kotlin で 作った closure を Groovy で扱う'() { def closure = ClosureCreation.stringToIntClosure(this, [get: { 'Groovy' }] as Supplier<String>) assert closure('Calling closure made by Kotlin.') == 'Groovy Calling closure made by Kotlin.'.size() } }
こちらは試していただければテストが通ることがわかるかと思います。
仕組み
Groovy の Closure
は java.util.concurrent.Callable
インターフェース と Runnable
インターフェースを実装した抽象クラスになっています。
そして、それぞれの実装は次のようになっています。
public void run() { call(); } public V call() { final Object[] NOARGS = EMPTY_OBJECT_ARRAY; return call(NOARGS); } public V call(Object... args) { return (V) getMetaClass().invokeMethod(this, "doCall", args); }
invokeMethod
という名前から想像がつく通り、最終的には doCall
メソッドを動的に取得して実行します。したがって、欲しいパラメーターの型と戻り値の型をつかった doCall
メソッドを object
式で作れば、 Groovy でなくても Closure
のインスタンスを生成できるという事になります。
また、先程までの説明ではプロパティは〜〜から取得するという説明をしましたが、これを実現するのが Closure
の delegate
です。何かしらのメソッド、フィールド(プロパティ)を Closure
内で取得する時の取得元になります。これは Kotlin のレシーバーつき関数(Foo.(Bar) -> Baz
)のようなものです。Gradleのドキュメントなどで Closure
を受け取るメソッドについては、たいてい 〜〜 is passed as the closure's delegate のように書かれていたりします。
応用
先日ブログに書いた、 junit-starter-plugin においても、ここで書いた変換を行っています。
gradle-plugin-publish-plugin の DSL にて PluginBundleExtension
を介して gradle plugin の id(plugins
ブロックで指定するやつ)と displayName を設定しますが、PluginBundleExtensions
内部の plugins
ブロックの引数が groovy.lang.Closure
であるため、Kotlin の関数・レシーバーつき関数ではここを記述できないため、ここに用いた変換用の関数を使っています。
おわり