2008年3月28日金曜日

Ruby のブロック付きメソッドとイテレータ - yield の様々な使い方

1. 要素を取り出す each メソッド

Ruby の「イテレータ」は、複数の要素を持つオブジェクトが、個々の要素を取り出し、何らかの処理を適用するときに使うと便利。

一番基本となるメソッドが each 。例えば、Array クラスの場合、each メソッドで、要素をひとつづづ取り出すことができる。

[1,2,3,4,5].each do |i|
  puts i
end

これはどういう仕組みで、このように書けるのだろうか?

 

2. イテレータの役割

オブジェクト指向スクリプト言語 Ruby」(p94) では、イテレータについて、次のように述べられている。

イテレータ (Iterator) はメソッドの一種で、もともとは繰り返しの抽象化のためのものでした。

しかし、はじめから 「繰り返しの抽象化のため」 という視点で、イテレータを理解しようとすると、わかりずらい。「繰り返しの処理のため」にあるというよりは、

関数 (処理) をメソッドに渡すための仕組み

と理解しておく方がよい。

 

3. yield の意味

イテレータに関して、最初に覚えておく単語は、

yield

これは、渡された関数に処理を移すための命令。もっと端的言うなら、yield が書かれたメソッドに渡されたブロックを、実行するための手段。Ruby では、関数と言わずに 「ブロック」 と呼ぶので、以降ブロックと呼ぶ。

ちなみに、Yahoo!辞書 - yield を調べると、その中の 1 つの意味として、

3 [III[名]([副])] …を放棄する, 手放す, (…に)明け渡す((up/to ...)); …

((~ -self))(誘惑などに)身を任せる, ふける((to ...));

[V[名](as)[名]] 身を任せて(…に)なる...

They yielded (up) the city [=the city (up)] to the enemy.

町を敵の手にゆだねた

Ruby の yield に当てはめて意味を考えると、

渡されたブロックに処理を「明け渡す、身を任せる」

と言った意味として捉えておけばいいかな。

 

4. 引数の数を変えてyield を呼び出してみる

引数なしで yield を呼び出す

渡されたブロックを処理する関数 test を定義してみる。

def test
  yield
end

test{puts "hoge"}

test を呼出すと、呼出したときに渡したブロックが yield によって呼出される。

 

yield に引数を1つ渡す

yield に引数を与えると、yield が書かれたメソッドに渡されたブロックの、ブロック変数に引数が渡される。

ブロック変数とは、ブロックの先頭で

|変数|

と記述されている変数のこと。

def test2
  yield "hoge"
end

test2{|x| puts x}

 

yield に複数の引数を渡す

yield に引数を複数渡すと、それに対応したブロック変数に渡される。

def test3
  yield "hoge", 100
end

test3{|x, y| print x, y, "\n"}

 

5. block_given? メソッドで、ブロックのある・なしに対応

これまでは、メソッドに必ずブロックがある場合を試した。これを、ブロックが渡される場合と、渡されない場合に対応できるメソッドに変更する。

def test4
  if block_given?
   yield
  else
   puts "no blcock"
  end
end

test4{puts "hoge"}
test4

 

6. 要素を取り出す、イテレータとして用いる yield

yield を複数回呼び出す

yield は、渡されたブロックを実行するための手段であることが実感できた。

次は、「要素を走査するイテレータ」の使い方を、yield を用いて書く。

def test5
  for i in [1,2,3]
   yield
  end
end

test5{puts "hoge"}

for ループにより、yield が 3 回実行されている。これにより、渡したブロックが 3 回される。

 

yield を複数回呼び出すとき、引数を一つ渡す

上記を少しだけ変更する。 yield を呼出す際、for ループで使われている変数 i の値を渡す。

def test6
  for i in [1,2,3]
   yield i
  end
end

test6{|x| puts x}

これにより、先ほどと同じく、渡されたブロックが 3 回実行されるが、その度にブロック変数に渡される値が異なる。

 

yield を複数回呼び出すとき、引数を複数渡す

yield を複数回呼び出す。今度は値を複数渡してみる。

def test7
  for i in [1,2,3]
   yield i, "hoge"
  end
end

test7{|x,y| print  x, y, "\n"}

 

7. 「引数」と「ブロック」を受け取るメソッドの定義

引数を受け取るメソッドを定義する。ただし、ブロックも受け取る。

以下のメソッドでは、引数の値に応じて、ブロックの呼出しを変化させている。

def test8(val)
  for i in [1,2,3,4,5]
   yield i, "hoge"  if i > val
  end
end

test8(0){|x,y| print x, y, "\n"}
test8(2){|x,y| print x, y, "\n"}

このように、Ruby では、直接関数を渡すのではなく、「ブロック」を利用するところに特徴がある。慣れないと、素直に関数を渡せた方がシンプルでいいのになと感じる。 ^^;

Ruby のイテレータ (2) – Enumerable と Comparable モジュール」へつづく…

1コメント:

タニイ さんのコメント...

イテレーターが使い方がよくわからなかったのですが、具体例をコマンドプロンプトで試したら理解できました。

ありがとうございます。