久しぶりの投稿です。相変わらず Rubyist です。よろしくお願いします。
なぜ内側から外側のループを redo したいの?
内側ループの中でエラーが生じたときに、外側ループを同じループでやり直しをさせたいことがあったのです。
まずは redo
の復習
redo
って何だっけ?滅多に使わないから忘れるよね。
redo
例:
redo
文法:
redo
ループ条件のチェックを行なわず、現在の繰り返しをやり直します。
制御構造 (Ruby 2.4.0)
常に redo
するなんてあり得ないので if
修飾子つけたり (redo if ~~
) など、何かの条件を付けて書きますよね。
あと念のためですが
ループとは
while
until
for
- イテレータ
のいずれかを指します。
ループ構造をネストさせると redo
はどこをやり直す?
さきほどの記載にも
現在の繰り返しをやり直します。
とあったように、いまの繰り返し(ループ)をやり直します。つまりは最も内側のループをやり直します。
外側のループを redo
したいのだけど
大域脱出(catch
と throw
)を使います。
具体的には Kernel.#catch
のブロックを目的のループのすぐ内側に作ります。
catch
と throw
の復習
result = catch do |tag| for i in 1..2 for j in 1..2 for k in 1..2 throw tag, k end end end end p result #=> 1
throw
は同じ tag を指定したcatch
のブロックの終わりまでジャンプします。
module functionKernel.#throw
(Ruby 2.4.0)
上記の例では、throw tag, k
によって catch
ブロックの終わりの end
までジャンプします。throw
の第二引数 k
が catch
ブロックの返値になるので、変数 result
は 1
になっています。
では実際に外側ループの redo
をやってみよう
外側ループを5周させますが、内側ループ(3周)の間にエラーが生じたら外側ループをやり直しさせます。
5.times do |i|
redo_flag = catch :redo_outer_loop_from_deep_loop do
3.times do |j|
begin
p [i, j]
raise RuntimeError if rand(10) == 0
rescue => ex
throw :redo_outer_loop_from_deep_loop, ex
end
end
end
p redo_flag #debug
redo if redo_flag.class == RuntimeError
end
試しに実行させてみます。
[0, 0]
[0, 1]
#<RuntimeError: RuntimeError> # debug 出力
[0, 0]
[0, 1]
[0, 2]
#<RuntimeError: RuntimeError> # debug 出力
[0, 0]
[0, 1]
[0, 2]
3 # debug 出力
[1, 0]
[1, 1]
#<RuntimeError: RuntimeError> # debug出力
[1, 0]
[1, 1]
[1, 2]
3 # debug 出力
[2, 0]
#<RuntimeError: RuntimeError> # debug 出力
[2, 0]
[2, 1]
[2, 2]
3 # debug 出力
[3, 0]
[3, 1]
[3, 2]
3 # debug 出力
[4, 0]
#<RuntimeError: RuntimeError> # debug 出力
[4, 0]
[4, 1]
[4, 2]
3 # debug 出力
=> 5
外側ループを5周させることが出来ていますし、内側ループのなかで RuntimeError
エラーが生じたときには外側ループをやり直しさせることも出来ています。
なお debug 出力として 3
が返っているのは、内側ループが 3.times
ブロックであり、その返値が 3
になるためです。
おねがい
もうちょっとキレイな書き方があれば教えてください。