コンテナ、ブロック、イテレータ(3) 「プログラミング Ruby 1.9 - 言語編 - 4章」
ずいぶん間が空いてしまいましたがピッケル本1.9 再開です。
- 作者: Dave Thomas with Chad Fowler and Andy Hun,まつもとゆきひろ,田和勝
- 出版社/メーカー: オーム社
- 発売日: 2010/05/26
- メディア: 単行本(ソフトカバー)
- 購入: 4人 クリック: 256回
- この商品を含むブログ (25件) を見る
エントリに間があった期間は業務でテキスト処理ばかりでしたよ。
# めんどくさくなってFile.open()をラップする始末
さてさて、4章は残すところイテレータ以外のブロックの使い道についてだけです。
... とはいえ、lambdaを使ったクロージャなど、本のボリュームとは反比例の内容の濃さでした。
トランザクションとしてのブロック
ファイルストリームやDBの接続など、クローズし忘れのエラーを回避したい場合などに使う方法。
selfを使って,クラスメソッドとして実装します。
また,内部で使うメソッドに渡す引数を意識しなくてよいように*argsを引数とします。
例はこんな感じ*1。
class File def self.open_and_process(*args) f = File.open(*args) yield f f.close() end end
オブジェクトとしてのブロック
ブロックは無名のメソッドであるだけではなく、オブジェクトに変換することが可能です。
ブロックを受け付けるメソッドの最後の引数にアンパサンドを付けることで、
ブロックを明示的な引数としてメソッドで取り扱うことができます。
この際、引き渡されたブロックは"Procオブジェクト"として変換されます。
このProcオブジェクトは、必要に応じてcallメソッドを用いることでブロック内のコードを呼び出すことができます。
class ProcExample def pass_in_block(&action) @stroed_proc = action end def use_proc(paramater) @stroed_proc.call(paramater) end end eg = ProcExample.new eg.pass_in_block {|param| puts "The parameter is #{param}"} eg.use_proc(99) #=> The parameter is 99 eg.use_proc(100) #=> The parameter is 100
さらに、メソッドがProcオブジェクトを返すと以下のようにcallメソッドでパラメタを渡すことができます。
def create_block_object(&block) block end bo = create_block_object {|param| puts "You called me with #{param}"} bo.call 99 #=> You called me with 99 bo.call "cat" #=> You called me with cat
この方法は便利な方法なので
わざわざメソッドを定義しなくとも、組み込みのメソッド lambdaかProc.newを使うことで
ブロックをオブジェクトに変換することができます*2。
bo = lambda {|param| puts "You called me with #{param}"} bo.call 99 #=> You called me with 99 bo.call "cat" #=> You called me with cat
クロージャとしてのブロック
ブロックの外側スコープにあるローカル変数を参照できることを利用して、
def n_times(thing) lambda {|n| thing *n } end p1 = n_times(23) p1.call(3) #=> 69 p1.call(4) #=> 96
のようにクロージャとして利用することができます。
この際、ブロック(Proc オブジェクト)が存在している限りアクセス可能で、
def power_proc_generator value = 1 lambda { value += value} end power_proc = power_proc_generator puts power_proc.call #=> 2 puts power_proc.call #=> 4 puts power_proc.call #=> 8
のようにvalueが生き続けるので累積して加算することができます。
別の表記法
Ruby 1.9からは lambda を -> と書けます*3。
proc1 = -> arg {puts "In proc1 with #{arg}"} proc2 = -> arg1, arg2 {puts "In proc2 with #{arg1} and #{arg2}"} proc1.call "ant" #=> In proc1 with ant proc2.call "dog", "cat" #=> In proc2 with dog and cat
ただし、
proc1 = lambda {|arg| puts "In proc1 with #{arg}"} proc2 = -> {|arg| puts "In proc2 with #{arg}"} proc1.call "cat" #=> In proc1 with ant proc2.call "cat" #=> proc.rb:53: syntax error, unexpected tSTRING_BEG, expecting keyword_do or '{' or '('
と、ブロック内引数として書くとsyntaxエラーになるようです。
文法の説明は以下の「ブロックの引数リスト」にて改めて見てみます。
この不思議な略式lambdaはメソッドにProc オブジェクトを渡す時に好まれて使われるようです。
def my_if(condition, then_clause, else_clause) if condition then_clause.call else else_clause.call end end 5.times do |val| my_if val < 3, -> {puts "#{val} is small"}, -> {puts "#{val} is big"} end #=> 0 is small #=> 1 is small #=> 2 is small #=> 3 is big #=> 4 is big
条件で表記を切り替えるためになんでこんな面倒な書き方を... とも思いますが、
ブロック内のコードをいつでも再評価できることが利点になります。
例として上がっているのはwhileループを実装し直した物でした。
while条件として渡すブロックが複雑になったり、動的に変化する場合にも対応できるのが利点ですね。
def my_while (cond, &body) while cond.call body.call end end a = 0 my_while -> {a < 3} do puts a a += 1 end
ブロックの引数リスト
Ruby 1.9からは、ブロックにも引数リストを指定できるようになったようです。
- lambdaには従来のブロック構文同様||で囲んだ引数リストを渡す
- -> にはブロックの前に個々の引数リストを渡す *4
proc1 = lambda do | a, *b, &block| puts "a = #{a.inspect}" puts "b = #{b.inspect}" block.call end proc1.call(1,2,3,4) {puts "in block1"} #=> a = 1 #=> b = [2, 3, 4] #=> in block1
同様に
proc2 = -> a, *b &block do puts "a = #{a.inspect}" puts "b = #{b.inspect}" block.call end