4. ブロック付きメソッド¶
もともとは、配列などのデータ構造の各要素に対して、繰り返し同じ処理をするために考えられた。そのため、以前はイテレータと呼ばれていた。
4.1. ブロック¶
do
... end
または {
... }
の形をしたもの。
x.each do puts 'Hello world' end
x.each { puts 'Hello world' }
do
... end
と {
... }
は意味は同じだが、構文的な結合の強さが違うので注意。
foo bar do puts 'Hello world' end # foo(bar) do puts 'Hello world' end と同じ
foo bar { puts 'Hello world' } # foo( bar { puts 'Hello world' } ) と同じ
[1, 2, 3].inject 0 do |s, i| s += i end # => 6
[1, 2, 3].inject 0 { |s, i| s += i } # エラー
注釈
while
や for
の後の do
... end
はブロックではない(スコープを作らない)ので注意。スコープについては ローカル変数のスコープ を参照。
4.2. ブロックの引数¶
ブロックの先頭に縦棒で囲んで変数名を書くと、ブロックの引数になる。メソッド(関数)定義の引数とだいたい同じ。
引数に何が渡されるかはメソッドによって違う。
x = [ 1, 2, 3 ]
sum = 0
x.each do |i| # i に配列の各要素が代入される
sum += i
end
x = [ 1, 2, 3 ]
x.inject(0) do |sum, i| # 前の繰り返しの結果が sum に、次の要素が i に代入される
sum + i
end
IO.foreach('words.txt') do |line| # line にファイルの各行が代入される
puts line
end
open('random.txt', 'w') do |f| # f にファイルハンドラが代入される
f.puts rand(100)
end # ブロックの終了で自動的にクローズ
課題6
Gutenbergプロジェクトの単語リスト をダウンロードして CROSSWD.TXT
というファイルに保存せよ。この中で最も長い単語を探して出力するプログラムを書け。ただし、文字列 line
の長さは line.length
でわかる。同じ長さの単語が複数ある場合は、どれか一個だけ出力すればよい。
4.3. ローカル変数のスコープ¶
Rubyでは明示的な変数宣言はなく、最初に代入した時に宣言したことになる。
ローカル変数のスコープは、宣言(最初の代入)を含む最も内側のブロックやメソッド定義の中だけ。
def foo
x = 1 # x は foo の中で宣言
[1, 2, 3].each do |i| # i はブロックの中で宣言
puts x # x はfooの中で有効なので大丈夫
end
puts i # i はブロックの中で有効なので外で使うとエラーになる
end
注釈
ブロックは外側のローカル変数も使えるが、メソッド定義は外側のローカル変数は使えない。
def foo
x = 1 # x は foo の中で宣言される
def bar
puts x # foo と bar は違うスコープなのでエラーになる
end
bar
end
4.4. ブロック付きメソッドの作り方¶
ブロック付きメソッドの定義の仕方は普通のメソッドと同じ。メソッド本体の中で yield
を実行すると、そこで指定した値をブロックのパラメータに渡して、ブロック内部を実行する。
def foo
yield 1
yield 2
yield 3
end
foo do |x|
puts x
end
def foo(x, y)
yield x, y
end
foo(1, 2) do |i, j|
puts i+j
end
ブロック付きメソッドは、コールバック関数を簡単に書けるようにしたものと言える。他の言語だと関数を明示的に渡すことが多い。上の例を、関数を明示的に渡すように書きなおすと次のようになる。
def foo(func)
func.call 1
func.call 2
func.call 3
end
foo( lambda{ |x| puts x } )
def foo(x, y, func)
func.call x, y
end
foo(1, 2, lambda{ |i, j| puts i+j })
課題7
2個の配列とブロックを受け取り、配列の各要素ごとにブロックで指定された演算を行うメソッド arraycalc
を作れ。
例えば、 arraycalc([1,2,3], [4,5,6]){|x,y| x*y}
は、まず x=1
と y=4
に対して x*y
を計算して4、他の要素は同様に10, 18となるので、配列 [4,10,18]
を返す。
ただし、引数が配列でない場合や、配列の要素の個数が一致しない場合は考えなくてよい。