[ruby-list:154] TUTORIAL - iteraters

From: matz@... (Yukihiro Matsumoto)
Date: 1996-02-15 01:32:42 UTC
List: ruby-list #154
まつもと ゆきひろ@トヨタケーラムです.

前にもほぼ同内容を流しましたし,既にホームページには載ってい
たりするんですが,「イテレータ」についてです.
--
* イテレータってなに?

イテレータっていうのは別に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

`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
 ruby| 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()のようなイテレータ
は実はあまり使えない.後でクラスの話をした後で,実際に使える
イテレータの話をすることにしよう.

In This Thread

Prev Next