Groovyでdo-whileループを!
はじめに
No 'do ... while()' syntax as yet.
Groovyにはdo-whileループがない…
do ... while() 構文はまだないよ!
Due to ambiguity, we've not yet added support for do .. while to Groovy
曖昧だもん、だからGroovyのサポートにはまだdo ... while()ループは追加してないんだからね!
だそうです。
そこで作ってみました。
きっかけ
これは完全に盲点だった。Objectを拡張しちゃえばコード中どこでも使えるではないか。そう、まさにprintlnとかがやってることはこれなのだ。
ということで早速実装
ソース(参照)
Object.metaClass.doloop = { proc ->
[ // whileの実装
'while': { cond ->
proc()
while(cond()) {
proc()
}
},
// untilの実装
'until': { cond ->
proc()
while(!cond()) {
proc()
}
}
]
}
// 以下、デモ
def a = 1
doloop {
println "[in do-while loop] $a"
a++
}.while{ a < 10 }
doloop {
println "[in do-until loop] $a"
a++
}.until{ a == 20 }
できた!ついでにuntilも付け加えてみた。
while() じゃなくて .while{ 条件 }*1, .until{ 条件 }*2 なのはご愛嬌。
よくわかる解説
- doloopメソッドは引数としてproc(処理)の実体を行うクロージャを受け取る。なので doloop { ... } みたいな書き方となる。
- 受け取った procクロージャはすぐには実行せずに取っておく。
- doloopメソッドはキーとして 'while' と 'until' を持ったマップを返却する。
- doloopメソッドが返却するマップに格納されているのは先ほど受け取ったprocクロージャを実行するクロージャである。
- マップに対してキー、'while'で取得できるクロージャは引数としてcondクロージャを受け取る。このcondクロージャは終了条件として使われる。
- 'while'の場合は、proc()を実行した後、cond()の結果が真の間繰り返すよう、whileループを行う。
- 'until'の場合は、proc()を実行した後、cond()の結果が真になるまで繰り返すよう、whileループを行う。
以上。
おわりに
これはDSLチックな感じなので面白いんだけど多分使わないな。
追記1 (2011-06-30)
id:backpaper0さん、Thanks!
Scalaを参考にして例外処理でbreak実装してみますた。https://gist.github.com/1056495 RT @fumokmm: @kimukou_26 breakは試してないですが、うまいことやればいけるかもしれんですね。試してみてくださいw
これは手っ取り早い良い方法ですね。
追記2 (2011-07-02)
以前書いたGroovyでクロージャ内部で設定してもらった値を利用する - No Programming, No Lifeを参考に一時オブジェクトを利用したdoBreak, doContinueをクロージャにくっつけてみました。
ソース(参照)
Object.metaClass.doLoop = { proc ->
def temp = new Object() {
def e = new Exception() // 一時的な例外
def doBreak = { throw this.e } // doBreak() の形でeをthrowできるようにする
def doContinue = { throw this.e } // doContinue() の形でeをthrowできるようにする
}
proc.delegate = temp // proc内でtempを使えるようにする
[ // whileの実装
'while': { cond ->
try {
proc()
while(cond()) {
proc()
}
} catch(ex) { if (!ex.is(temp.e)) throw ex }
},
// untilの実装
'until': { cond ->
try {
proc()
} catch(ex) { if (!ex.is(temp.e)) throw ex }
while(!cond()) {
try {
proc()
} catch(ex) { if (!ex.is(temp.e)) throw ex }
}
}
]
}
// 以下、デモ
Object.metaClass.lnprint = { println(); print it }
def a = 1
print "[1.In do-while loop]".padRight(35)
doLoop {
print "[$a]"
a++
}.while{ a < 10 }
lnprint "[2.In do-while loop with break]".padRight(35)
doLoop {
print "[$a]"
if (a == 15) doBreak() // 15になったらbreak
a++
}.while{ a < 20 }
lnprint "[3.In do-until loop]".padRight(35)
doLoop {
print "[$a]"
a++
}.until{ a == 25 }
lnprint "[4.In do-until loop with continue]".padRight(35)
doLoop {
a++
if (a < 35) doContinue() // 35より小さい場合はcontimue
print "[$a]"
}.until{ a == 40 }
Groovyの再帰処理(trampoline)についてひとつ分かったこと
はじめに
Groovyのクロージャ(Closure)には.trampolineってメソッドがあって、これをうまい感じに使うと末尾再帰を最適化してくれる。
よしやってみよう
では、簡単な合計値を求めるサンプルを再帰処理で。
java.lang.StackOverflowError
お約束。
よし、今こそトランポリンだ!
groovy.lang.MissingMethodException: No signature of method: java.lang.Integer.plus() is applicable for argument types: (groovy.lang.TrampolineClosure) values: [groovy.lang.TrampolineClosure@509c6c30]
あれ?Integer.plus()?あ…もしかして…。
これが最後の恋であるように*1
ふぅ、やっと動いた。trampolineで遊ぶときは、ほんとに最後の最後はtrampolineしか呼んじゃダメってことなんですね…。trampolineを呼ぶときは最後の恋と覚えましょう(謎)
考察
リストが来たらカーしたものとクダーを再帰に渡して返って来た結果をプラスして返すっていう常套手段がこれでは使えない。最後の例では仕方なくsumという変数を引き渡すようにして対応した。うーん、なんとかならんもんだろうか。
まぁ、それはそれとして最後にまとめておくか。
trampolineする際の大人のマナー
- クロージャ内の最後の部分で引数ありtrampolineのみを呼び出す
- 内部でtrampolineしているクロージャを使う場合は、引数なしtrampolineメソッドを呼び出して、trampolineできるようにしてから使う
