web-dev-qa-db-ja.com

Groovyクロージャーで「継続」をシミュレートするための最適なパターン

Groovyはクロージャー内からのbreakcontinueをサポートしていないようです。これをシミュレートする最良の方法は何ですか?

revs.eachLine { line -> 
    if (line ==~ /-{28}/) {
         // continue to next line...
    }
}
67
talanb

サポートできるのは、中断することなく、きれいに続けることだけです。特にeachLineやeachのようなもの。ブレークをサポートできないことは、それらのメソッドがどのように評価されるかに関係しており、メソッドと通信できるループを終了しないことを考慮していません。継続サポートの方法は次のとおりです-

最善のアプローチ(結果の値は必要ないと仮定)。

revs.eachLine { line -> 
    if (line ==~ /-{28}/) {
        return // returns from the closure
    }
}

サンプルが本当に単純な場合、これは読みやすさの点で優れています。

revs.eachLine { line -> 
    if (!(line ==~ /-{28}/)) {
        // do what you would normally do
    }
}

別のオプションは、バイトコードレベルでコンティニューが通常行うことをシミュレートします。

revs.eachLine { line -> 
    while (true) {
        if (line ==~ /-{28}/) {
            break
        }
        // rest of normal code
        break
    }

}

ブレークをサポートする1​​つの方法は、例外を使用することです。

try {
    revs.eachLine { line -> 
        if (line ==~ /-{28}/) {
            throw new Exception("Break")
        }
    }
} catch (Exception e) { } // just drop the exception

特にNumberFormatExceptionsやIOExceptionsのような実際の例外をスローする可能性のあるクラスで他の処理が行われている場合、他の実際の例外のマスクを避けるために、カスタム例外タイプを使用できます。

71
shemnon

クロージャーは、ループ/反復構造ではないため、中断または続行できません。代わりに、それらは反復ロジックの処理/解釈/処理に使用されるツールです。次のように処理せずにクロージャから戻るだけで、指定された反復を無視できます。

revs.eachLine { line -> 
    if (line ==~ /-{28}/) {
            return
    }

}

ブレークサポートはクロージャーレベルでは発生しませんが、代わりにクロージャーを受け入れたメソッド呼び出しのセマンティクスによって暗示されます。つまり、コレクション全体を処理することを目的としたコレクションのようなものに対して「each」を呼び出す代わりに、特定の条件が満たされるまで処理するfindを呼び出す必要があります。クロージャから抜け出す必要があると感じる(ほとんどの場合)ほとんどの場合、本当にやりたいことは、反復中に特定の条件を見つけ、findメソッドが論理的なニーズだけでなく、意図にも一致するようにすることです。残念なことに、一部のAPIにはfindメソッドのサポートがありません...ファイルなど。言語にbreak/continueを含めるかどうかの議論に費やされたすべての時間は、これらの無視された領域にfindメソッドを追加することに費やされた可能性があります。 firstDirMatching(Closure c)またはfindLineMatching(Closure c)のようなものは大いに役立ち、「なぜ私は…から抜け出せないのか」の99 +%と答えます。メーリングリストに表示される質問。そうは言っても、これらのメソッドをMetaClassまたはCategoriesを介して自分で追加するのは簡単です。

class FileSupport {
   public static String findLineMatching(File f, Closure c) {
      f.withInputStream {
         def r = new BufferedReader(new InputStreamReader(it))
         for(def l = r.readLine(); null!=l; l = r.readLine())
             if(c.call(l)) return l
         return null
      }
   }
}

using(FileSupport) { new File("/home/me/some.txt").findLineMatching { line ==~ /-{28}/ }

例外やその他の魔法を伴う他のハックは機能する可能性がありますが、状況によっては余分なオーバーヘッドが発生し、他の状況では読みやすさが低下します。本当の答えは、コードを見て、代わりに本当に繰り返し処理するのか検索するのかを尋ねることです。

15
Cliff

Javaで静的な例外オブジェクトを事前に作成してから、クロージャ内から(静的な)例外をスローする場合、実行時のコストは最小限です。実際のコストは例外の作成に発生しますMartin Odersky(Scalaの発明者)によると、多くのJVMは実際にthrow命令を単一ジャンプに最適化できます。

これは、ブレークをシミュレートするために使用できます。

final static BREAK = new Exception();
//...
try {
  ... { throw BREAK; }
} catch (Exception ex) { /* ignored */ }
10
Ralph

return tocontinueおよびany closure tobreak

ファイルの内容:

1
2
----------------------------
3
4
5

Groovyコード:

new FileReader('myfile.txt').any { line ->
    if (line =~ /-+/)
        return // continue

    println line

    if (line == "3")
        true // break
}

出力:

1
2
3
8

この場合、おそらくfind()メソッドを考える必要があります。クロージャが最初に渡された後に停止し、trueを返します。

3
0rt

rx-Java を使用すると、イテラブルをオブザーバブルに変換できます。

次に、continuefilterおよびbreakwithtakeWhile

以下に例を示します。

import rx.Observable

Observable.from(1..100000000000000000)
          .filter { it % 2 != 1} 
          .takeWhile { it<10 } 
          .forEach {println it}
1
frhack