読者です 読者をやめる 読者になる 読者になる

No Programming, No Life

新しいNPNLです。http://d.hatena.ne.jp/fumokmm/ から引っ越してきました。

Groovyでdo-whileループを!

Groovy DSLs 小ネタ

はじめに

No 'do ... while()' syntax as yet.
do ... while() 構文はまだないよ!
Due to ambiguity, we've not yet added support for do .. while to Groovy
曖昧だもん、だからGroovyのサポートにはまだdo ... while()ループは追加してないんだからね!

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 なのはご愛嬌。

よくわかる解説

  1. doloopメソッドは引数としてproc(処理)の実体を行うクロージャを受け取る。なので doloop { ... } みたいな書き方となる。
  2. 受け取った procクロージャはすぐには実行せずに取っておく。
  3. doloopメソッドはキーとして 'while' と 'until' を持ったマップを返却する。
  4. doloopメソッドが返却するマップに格納されているのは先ほど受け取ったprocクロージャを実行するクロージャである。
  5. マップに対してキー、'while'で取得できるクロージャは引数としてcondクロージャを受け取る。このcondクロージャは終了条件として使われる。
  6. 'while'の場合は、proc()を実行した後、cond()の結果が真の間繰り返すよう、whileループを行う。
  7. 'until'の場合は、proc()を実行した後、cond()の結果が真になるまで繰り返すよう、whileループを行う。

以上。

おわりに

これはDSLチックな感じなので面白いんだけど多分使わないな。

追記1 (2011-06-30)

id:backpaper0さん、Thanks!

Scalaを参考にして例外処理でbreak実装してみますた。https://gist.github.com/1056495 RT @fumokmm: @kimukou_26 breakは試してないですが、うまいことやればいけるかもしれんですね。試してみてくださいwThu Jun 30 15:39:55 via YoruFukurou


これは手っ取り早い良い方法ですね。

追記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 }

*1:条件が真の間繰り返し。

*2:条件が真になったら終了(真になるまで繰り返し)。