Ruby で Python の for else みたいなの
繰り返しを実行して break(あるいは return)されなかったら何かを実行するって時々欲しいのですけれど、Ruby では Python の for else みたいな構文がないのですよね。なので throw ~ catch を使って大域脱出したりするのだけれど、どうにかできないかと思って考えてみました。
Enumerator#with_else(f) {..} です。f は Proc オブジェクトです。each などの Enumerator をレシーバーとして、ブロック内で break が実行されなければ、繰り返しを終えたあとに f.call されます。ブロックがなければ Enumerator を返します。
こんな感じ。
f = proc do puts "break せずに実行されました!" end ar = [] 5.times {ar << rand(5)} ar.each.with_else(f) do |i| puts i break if i == 4 end
を実行すると
1 0 0 1 3 break せずに実行されました!
みたいな。break されると
0 4
という風に f は実行されません。
また、each_with_index などと組み合わせて
ar = [] 5.times {ar << rand(5)} ar.each_with_index.with_else(f) do |i, j| p [i, j] break if i == 4 end
で、
[0, 0] [0, 1] [1, 2] [1, 3] [0, 4] break せずに実行されました!
なんてのも可能です。
コードはこんな感じ。
with_else.rb
class Enumerator def with_else(f, *args) if block_given? each {|*i| yield(*i)} f.call(*args) else Enumerator.new do |y| each do |*i| a = (i.size <= 1) ? i[0] : i y << a end f.call(*args) end end end end
else 節の可変長引数の処理にちょっと悩みました。
じつは前にも考えたのだけれど、上のように落ち着きました。
でも、やっぱり throw ~ catch の方が見やすいですかね…。
Gem 化
調子にのって Gem にしました。まったく大したものではないですが…。(9/11)
with_else | RubyGems.org | your community gem host