イテレータを作ってみる

Rubyと言えばイテレータだ(ほんとか)。これを理解したい。イテレータと呼ばれてるものはブロック渡しメソッドと呼ぶほうが、いいようだ。ブロックとは処理の塊のことを指しているだけで、そのブロックにオブジェクトを1つまたは複数渡して、何か処理をするってことだと理解している。これって高階関数な人たちが適用と呼んでる操作で、ブロックって、つまり無名関数とかラムダ関数とか、クロージャと呼ばれてるものと同種のものじゃないかと思うけど、このへんどういう違いがあるのか良く分からない。

ともあれ、/usr/lib/ruby/1.8/以下にあるyield文を検索して眺めてみた。例えばfilter.rbには、

def each_line(rs = $/)
  while line = gets
    yield(line)
  end
end
alias each each_line

def each_byte
  while char = getc
    yield(char)
  end
end

というのがある。なるほど、1つずつ持ってきてブロック内の変数に突っ込んでいるのかな、と分かりやすげな例がある一方、WEBrickのserver.rbを見ると、

class SimpleServer
  def SimpleServer.start
    yield
  end
end

とかいうのがあって、意味が分からない。サーバのインスタンスを立ち上げているようにも見える。

まあ、そのうちもう少し勉強することにして、とにかく自分もyieldでなんかやってみたい。それで、前のエントリで書いたArrayクラスに足すpick_one!メソッドを、イテレータにしてみようと思った。元のメソッド定義は、これ。

class Array
  def pick_one!
    r = rand(self.size)
    e = self[r]
    self.delete_at(r)
    return e
  end
end

これをrand_eachというイテレータにしてみる。

class Array
  def rand_each
    while(self.size > 0)
      r = rand(self.size)
      e = self[r]
      self.delete_at(r)
      yield(e)
    end
  end
end

で、実行してみた。

irb(main):001:0> require 'y.rb'
=> true
irb(main):002:0> (0..9).to_a.rand_each{|i| puts i}
0
3
4
7
1
9
6
5
2
8
=> nil
irb(main):003:0> (0..9).to_a.rand_each{|i| puts i}
2
5
8
1
6
7
0
4
3
9
=> nil
irb(main):024:0> (0..100).to_a.rand_each{|i| print i,","}
8,47,28,75,77,78,6,9,100,64,45,16,92,23,29,54,17,37,93,86,
72,81,40,14,31,79,26,96,71,33,67,22,19,43,27,18,42,91,70,0,
83,35,57,10,30,11,49,4,80,60,85,24,12,20,32,44,25,53,87,90,
1,39,94,5,41,69,34,13,73,62,38,84,50,7,97,74,15,99,3,36,88,
98,95,56,61,46,21,68,76,82,55,58,52,66,51,59,89,65,48,2,63,
=> nil

動いた!

メデタシ、メデタシ……、とか一発で動いたような書き方だけど、実は「while(self.size > 0)」というのを「while self.size」と書いて無限ループに陥ったりした。Rubyで偽となるのはfalseとnilだけで、0は立派な数値オブジェクトなのだった。while(0)が無限ループかぁ……。なんか不便な気がするけど。

yieldの基本的な使い方は分かった気がするけど、どうもモヤモヤ感いっぱい。