Groovyで後置if

なんか後置ifがtwitter上で話題になってたのでやってみました。
文のうしろに「if ほげほげ」みたいに書くとほげほげが真の場合だけ文を実行してくれる、てやつですね。Groovy風にかくなら

def executed = false;

{->executed = true}.if('a' == 'A')
assert executed == false;

{->executed = true}.if('a'.equalsIgnoreCase('A'))
assert executed == true

て感じかな、と。つまりifてメソッドクロージャに追加して、条件を引数にしちゃえばええやん、と思いついたのです。
で、とりあえず流してみると

Exception thrown: No signature of method: ConsoleScript0.if() is applicable for argument types: (java.lang.Boolean) values: [false]

groovy.lang.MissingMethodException: No signature of method: ConsoleScript0.if() is applicable for argument types: (java.lang.Boolean) values: [false]
	at ConsoleScript0.run(ConsoleScript0:3)

そんなメソッドねーべ、とおこられます。こりゃ行けそう。とりあえず先頭でなんもしないメソッドを追加してみます。

Closure.metaClass.if = {boolean condition->
};
// 以上追加。以下は元々あったassertたち

def executed = false;

{->executed = true}.if('a' == 'A')
assert executed == false;

{->executed = true}.if('a'.equalsIgnoreCase('A'))
assert executed == true

これで動いて、2個目のassertで落ちるはず。

Exception thrown: Expression: (executed == true). Values: executed = false

java.lang.AssertionError: Expression: (executed == true). Values: executed = false
	at ConsoleScript3.run(ConsoleScript3:11)

はい、予想通り。じゃ、実装。conditionがtrueの場合だけクロージャ自身を実行する訳ですね。

Closure.metaClass.if = {boolean condition->
    if (condition) {delegate.call()} // 1行追加
}

def executed = false;

{->executed = true}.if('a' == 'A')
assert executed == false;

{->executed = true}.if('a'.equalsIgnoreCase('A'))
assert executed == true

実行してみると…

Exception thrown: No signature of method: ConsoleScript4.call() is applicable for argument types: () values: []

groovy.lang.MissingMethodException: No signature of method: ConsoleScript4.call() is applicable for argument types: () values: []
	at ConsoleScript4$_run_closure1.doCall(ConsoleScript4:2)
	at ConsoleScript4.run(ConsoleScript4:10)

ありゃ、なんか落ちました。なんかdelegateが「ConsoleScript4」てクラスになってるみたいです。
「ConsoleScript」はGroovyConsoleでスクリプトを書いた場合にスクリプト自体の為に作成されるクラスです。あれれ?Closureじゃないの?
ちょっと調べてみると…
[GROOVY-3674] In Closure as injected method of Closure, delegate is not collect - jira.codehaus.org

Setting the delegate correctly is something only ExpandoMetaClass does〜calling EMC.enableGlobally() before clos is getting its value. If you do that, then your example works too.

ふむ。EMC.enableGlobally() すりゃうまく行くよ、と。EMCはExpandoMetaClassを省略してるんですな。じゃ…

ExpandoMetaClass.enableGlobally();

Closure.metaClass.if = {boolean condition->
    if (condition) {delegate.call()}
}

def executed = false;

{->executed = true}.if('a' == 'A')
assert executed == false;

{->executed = true}.if('a'.equalsIgnoreCase('A'))
assert executed == true

はい、通りました。後置ifの完成です。

さて、トリッキーなところが2つあったので、別エントリで立てときます。