[ruby-list:102] TUTORIAL(iterater)
From:
matz@... (Yukihiro Matsumoto)
Date:
1996-01-30 10:18:11 UTC
List:
ruby-list #102
まつもと ゆきひろ@トヨタケーラムです.
ちょっと分かりにくいイテレータの話が出ましたので,書きかけの
TUTORIALからイテレータの部分を抜粋します.
# まだ分かりにくいだろうなあ.
--
* イテレータってなに?
イテレータっていうのは別に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; printf "<%c>", s[i] 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"; print l end
a
b
c
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
なんとなく,わかったかな.自分で新しくデータ構造を定義した時
には,そのデータ構造にふさわしいイテレータを定義しておくと便
利なことが多い.