uehaj's blog

Grな日々 - GroovyとかGrailsとかElmとかRustとかHaskellとかReactとかFregeとかJavaとか -

@BaseScriptアノテーションはscriptをインクルードするのに使えます

Groovy 2.2以降、@BaseScriptアノテーションというのが導入され、Scriptの基底クラスを指定することができる*1ようになりました。

これは主目的としては、専用目的の便利機能追加・DSLの作成だと思いますが、単にscriptをインクルードするのにも使えます。

例えば、以下の「OtherScript.groovy」をクラスパスの通ったところか、あるいはカレントディレクトリに配置しておきます。

// OtherScript.groovy
def methodOfOtherScript() {
    "i'm methodOfOtherScript1"
}

def methodOfOtherScript1() {
    "i'm methodOfOtherScript2"
}

println "this is otherScript!"
a=3

これを呼び出す、mainscript.groovyでは以下のように@BaseScriptアノテーションを指定します。

import groovy.transform.BaseScript
@BaseScript OtherScript _

super.run()
println methodOfOtherScript1()
println methodOfOtherScript2()
println a

すると以下のとおり。

$ groovy mainscript.groovy
this is otherScript!
i'm methodOfOtherScript1
i'm methodOfOtherScript2
3

バインディング変数も引き継がれます。

main側のスクリプトを起点には探索してくれないようですが*2、OtherScriptのメソッドが修飾無しでmainscript.groovyから呼び出せます。ちなみに呼びだされるクラスは、大文字で始まらないと認識してくれません。つまり「otherscript.groovy」では駄目でした。

これ、前から欲しいと思ってたんだよね。呼ばれる側をクラス定義+staticメソッドにしてstatic importするとか、でも元のスクリプトスクリプトとして実行できなくなるし、それならスクリプトのままならnewしてインスタンスメソッドとして呼ぶとか、でもバインディング変数の引き継ぎとか駄目だし、mixinとかではあまり上手くいかないし*3

でもこの方法でも、1つのスクリプトで複数のscriptをimportすることは、できませんね。
ここは、ScriptがTraitだったら良かったのにな*4、ってところでしょう。

(追記2014/4/26)
バインディング変数は引き継げませんが、@Delegate+@Fieldでこんなのも可能だと今気づいた。

import groovy.transform.*

@Field @Delegate OtherScript a = new OtherScript()
@Field @Delegate SomeScript b = new SomeScript()

println methodOfOtherScript1()
println methodOfOtherScript2()
println methodOfSomeScript1()
println methodOfSomeScript2()

これなら複数のスクリプトもincludeできますや。
こっちの方がいいかも^^;

*1:従来から、コマンドラインオプションやコンパイラコンフィグレーションでは指定できたが、スクリプト一つで完結する形で指定できるようになった。

*2:動的に明示的にクラスパスに追加すれば可能と思うが

*3:2.2でやはり導入された@DelegatingScriptを@BaseScriptに指定してやれ!と思ったがNPEで不可。バグかなんらかの制限んか。

*4:実際には@BaseScriptが複数親指定を許さないと駄目だから、単にTraitになってもできない。@BaseScript側の対応も必要。