イテレータっていうのは別にrubyだけの概念じゃない.例えば一部 で人気のあるCLUっていう言語にもある.Lispの世界でもイテレー タとは呼ばないけど,良く使われる.でも,一般には珍しい考え方 であるのは確かなので,一度きちんと説明しておこう.
イテレート(iterate)っていうのは英語で繰り返すっていう意味の 動詞だ.イテレータってのは,だから「繰り返すもの」ってことに なる.人によっては「繰り返し子」って呼ぶ人もいる.もっともこ んな人は今じゃ少数派だろうけど.
プログラムを書いているといろいろな局面で繰り返しのためのルー プを書くことがある.C言語とかだとforだのwhileだの使って,ルー プを記述することになる.
char *str; for (str = "abcdefg"; *str; str++) { /* 各文字に対する処理 */ }
こんな感じだ.`for(..)'の部分はイディオムのようなものだが, データの構造に関する知識を要求していて,ちょっとうっとうしい. 内部の知識を知らないとループひとつちゃんと書けないというのは, なんか低レベルな感じがする.もうちょっと高級な言語なら繰り返 しのための制御構造を持っていたりする.例としてshを考えてみよ う(shがCより高級かという話もあるが,少なくてもこの場合に関し てはそうだ).
for i in *.[ch]; do # 各ファイルに対する処理 done
ファイル名を一つずつ取り出したり,代入したりするようなこまご まとしたことは全部言語がやってくれている.やっぱり,こっちの 方がレベルが高い感じがするよね.
でも,まだ問題がある.言語がもともと知っているデータ型に関し て繰り返しの制御構造があるのは良いことだが,ユーザは自分で定 義したデータ型に対してはやはりC言語のように低レベルのループ を自分で書かなければいけないというのでは,嬉しさも半減だ.オ ブジェクト指向プログラミングをしていると,ユーザがどんどん新 しいデータ型を定義していくことになるから,更に問題になる.
そういうこともあって,オブジェクト指向言語はどれでも,たとえ ば繰り返しを制御するクラスをライブラリとして提供したりなど, それなりに工夫しているんだけど,rubyならもっと直接的に自分で 制御構造を作り出すことができる.このユーザ定義の制御構造のこ とをrubyではイテレータと呼んでいる.
例で見てみよう.rubyの文字列型はいくつかのイテレータを持って いるので,例としてはちょうど良い.
ruby> "abc".each_byte{|c| printf "<%c>", c}; print "\n" <a><b><c> nil
each_byteは文字列の一文字ずつに対して繰り返すイテレータだ.c というローカル変数に各文字が代入される.C風に書くとこうなる だろう.
ruby> s="abc";i=0 0 ruby> while i<s.length ruby| printf "<%c>", s[i]; i+=1 ruby| end; print "\n" <a><b><c> nil
イテレータを使った方がシンプルだし,それに多分速い.また将来 文字列の構造やアクセス方法が変更になった時にも追随できそうだ.
文字列の持っているもうひとつのイテレータは各行毎に処理を行う each_lineだ.
ruby> "a\nb\nc\n".each_line{|l| print l} a b c nil
文字列から行の区切りを見付けたり,部分文字列を生成したりなど, 面倒なことは全部イテレータがやってくれる.便利なものだ.
先程説明したfor文は`each'というイテレータを使って,繰り返し を実行する構文だ.文字列のeachはeach_lineと同じ動作をするの で,上のeach_lineの例をforを使って書き換えてみよう.
ruby> for l in "a\nb\nc\n" ruby| print l ruby| end a b c nil
`for'とイテレータの中では`retry'という制御構造を使うことがで きる.これはループをはじめから(イテレータの呼び出しから)やり 直す制御構造だ.
ruby> c=0 0 ruby> for i in 0..4 ruby| print i ruby| if i == 2 and c == 0 ruby| c = 1 ruby| print "\n" ruby| retry ruby| end ruby| end; print "\n" 012 01234 nil
rubyでは自分でイテレータを定義することもできる.つまり,制限 はあるが自分で制御構造を作ることができるわけだ.イテレータの 定義は普通のメソッドの定義と全く変わらない.
違うのはイテレータの定義の中ではどこかで`yield'の呼び出しが あるということだ.yieldはイテレータに渡されたブロックに制御 を移す.例として引数として与えられた数だけ繰り返す`repeat'と いうイテレータを定義してみよう.
ruby> def repeat(num) ruby| while num > 0 ruby| yield ruby| num -= 1 ruby| end ruby| end nil ruby> repeat(3) { print "foo\n" } foo foo foo nil
前に説明した`retry'を応用すれば自分でwhileと同じ働きをするイ テレータも定義できる.遅いので意味はあまり無いが.
ruby> def WHILE(cond) ruby| return if not cond ruby| yield ruby| retry ruby| end nil ruby> i=0; WHILE(i<3) { print i; i+=1 } 012nil
なんとなく,わかったかな.
自分で新しくデータ構造を定義した時には,そのデータ構造にふさ わしいイテレータを定義しておくと便利なことが多い.そういう意 味では,例題としてあげたrepeat()やWHILE()のようなイテレータ は実はあまり使えない.後でクラスの話をした後で,実際に使える イテレータの話をすることにしよう.