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 }     # エラー

注釈

whilefor の後の 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=1y=4 に対して x*y を計算して4、他の要素は同様に10, 18となるので、配列 [4,10,18] を返す。

ただし、引数が配列でない場合や、配列の要素の個数が一致しない場合は考えなくてよい。

【解答例】