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
ちょっと調べてみると…
[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つあったので、別エントリで立てときます。