遅延評価の話その3。
まずは前回の訂正。
遅延リストを使った方のバージョンなら実際に大きなリストが作られることはないと書いたけど、大きなリストのようなものはできてしまう。…と言うのも、遅延リストを変数に取っておいているので、使用済みプロミスがGCに回収してもらえない。評価済みプロミスは値をキャッシュしているので、結局読み終えた行でも最後まで残ってしまう。ああ失敗。
さて、Scheme学習シリーズの最後は、遅延リストの発明済み車輪である、Gaucheのutil.streamを軽く転がしてみよう。Let's
(use util.stream)
まず名前。これまで遅延リストと呼んでいたものは、util.streamライブラリではストリーム(stream)と呼ばれている。そして、このライブラリが提供する名前にはstream-というプリフィックスが付けられている。
ストリームを操作する関数は、リストを操作する関数のストリーム版が大体用意されているので、mapやfilterを使う感覚でstream-mapやstream-filterを使えばよい。
ストリームを作成する関数も色々用意されている。ファイル読み出しにはport->streamを使えばよい。前回のlazy-read-linesに相当することは
(port->stream i read-line)これだけで実現できる。ちなみにread-lineを省略すると、read-charを用いて1文字ずつ読み出すストリームになる。
簡単な等差数列ならstream-iotaで作成可能。
(stream-iota 5) ; 0, 1, 2, 3, 4第1引数がストリームの長さで、負の値を指定すると無限長となる。第2引数は初期値で、省略すると0。第3引数がステップで、省略すると1。
(stream-iota -1) ; 0, 1, 2, 3, 4, ...
(stream-iota -1 10) ; 0, 10, 20, 30, 40, ...
(stream-iota -1 1 2) ; 1, 3, 5, 7, 9, ...
stream-tabulateを使えば、もう少し複雑なものも作れる。
(stream-tabulate 5 sqrt)) ; 0, 1, 1.414, 1.732, 2第1引数がストリームの長さで、負の値を指定すると無限長。第2引数がストリームの値を生成する関数で、引数は0, 1, 2, ...とストリームを読み進めるに連れてインクリメントされていく。
(stream-tabulate -1 (lambda (x) (* x x))) ; 0, 1, 4, 9, 16, ...
util.streamの実装は*load-path*のどこかにutil/stream.scmとして置いてあるので、眺めてみると参考になる。
0 件のコメント:
コメントを投稿