ブロック、イテレータ

目次

ブロック、イテレータ

メソッドの引数の直後にブロックを渡すと、そのブロックをメソッド内で呼び出せるようになる。
メソッド内部でyieldを実行すると渡されたブロックの実行が行われる。

def threeTimes
    yield
    yield
    yield
end
threeTimes { print "test" } ⇒ testtesttest

ブロックが渡されたかどうかは組み込み関数のiterator?で判断する。
またyieldに引数を渡すとブロックの始めの|・・・|で受け取ることができる。

def fibonacci( max )
    i = j = 1
    if iterator?
        while i <= max
            yield( i )
            i, j = j, i + j
        end
    else
        array = Array.new()
        fibonacci( max ) { |n| array.push( n ) }
        return array
    end
end
fibonacci( 1000 ){ |n| p n } ⇒ 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987
p fibonacci( 1000 ) ⇒ [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987]

fibonacciはブロックが渡されたときはイテレータを実行し、渡されなかったときはArrayを返す。

さらに、yieldはブロックの最後の値を返り値として受け取ることができ、 Array.findも簡単に実装できる。

class Array
    def find
        each do |i|
            return i if yield( i )
        end
    end
end
max = 30
print [ 1, 2, 3, 4, 5, 6 ].find{ |v| v * v > max } ⇒ 6

以上の例は「繰り返し」的な操作だったが、以下のような例も重要である。

class File
    def File.openAndProcess( *args )
        f = File.open( *args )
        yield f
        f.close
    end
end
File.openAndProcess( "data", "r") do |file|
    print while file.gets ⇒ first second third
end

この例はファイルのオープン、処理、クローズをイテレータにより一つのメソッドでしている。
File.openAndProcessは引数を配列argsとして受け取り、File.openに配列argsを展開して渡している。
記述は同じ*argsだが受け取る時と渡すときで逆の操作をしていて、結果として「引数をそのまま渡す」ことを実現している。

サンプル

ファイルロックなどの処理に力を発揮する方法なので、RuBBS で配布されているRuBBSに含まれるlock.rbを参考のために載せておく(解説はしない)。

#
# Lock module
#
# Copyright (C) 2001, All right reserved by TADA Tadashi <sho@spc.gr.jp>
# You can redistribute it and/or modify it under GPL2.
#
module Lock
    class LockError < StandardError; end
    
    def Lock::lock( basefile = 'lock/lock', try = 10, timeout = 600 )
        locking = false
        lockfile = "#{basefile}.#$$.#{Time.now.to_i}"
        try.times do
            begin
                File.rename( basefile, lockfile )
                locking = true
                break
            rescue #rename failed
                begin
                    Dir.glob( "#{basefile}.*" ).each do |oldlock|
                        if /^#{basefile}\.\d+\.(\d+)/ =~ oldlock and
                                Time.now.to_i - $1.to_i > timeout then
                            File.rename( oldlock, lockfile )
                            locking = true
                            break
                        end
                    end
                rescue
                end
            end
            sleep( 1 )
        end
        raise Lock::LockError, "LockError: #{lockfile} #{$!}" unless locking
        if iterator? then
            begin
                yield
            ensure
                Lock::unlock( lockfile, basefile ) if locking
            end
        else
            return lockfile
        end
    end
    
    def Lock::unlock( lockfile, basefile = 'lock/lock' )
        File.rename( lockfile, basefile )
    end
end

多分Perlメモ排他制御(ファイルロック)をするのRuby化+αだろう。
モジュール、例外処理等まだ出てきてないものも使っている。

スポンサード リンク

トラックバック

トラックバックURL
https://linux-life.net/tb/program/ruby/doc/chapter06/
Linux Life 〜 No linux, No life 〜
プログラミング > Ruby > Ruby入門 > ブロック、イテレータ