イテレータを作ってみる
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の基本的な使い方は分かった気がするけど、どうもモヤモヤ感いっぱい。