QA@IT

遅延シーケンスとストリーム

3140 PV

Gaucheでは無限列の実装として遅延シーケンス(Lazy sequences , gauche.lazy)とストリーム(Stream library , util.stream)があります。この2つをどのように使い分けていけばいいのでしょうか?

2つの実装の性能の違いや利用上の注意点を知りたく思い質問させていただいきました。
つまり、遅延シーケンスに適した状況、ストリームに適した状況とはなにかということです。

回答

util.streamはSchemeの共通仕様(srfi-40)に基づいたライブラリで、Lazy sequenceはGauche独自の実装です。簡単に特徴をまとめると次のようになります。

  • util.stream はポータブル: Schemeの標準機能を使ってutil.streamの機能は実装できます。たとえ処理系がutil.streamの機能を直接サポートしていなくても、srfiの参照実装を移植できるでしょう。従ってutil.streamを使って書いたプログラムは、Gauche以外のSchemeでも簡単に使える可能性が高いです。(注意:とはいえ、実はストリームの仕様としてより新しいsrfi-41があり、srfi-40は古くなってしまっています)
  • util.streamは落とし穴が少ない: util.streamの実装はまっとうな(教科書的な)遅延ストリームの実装で、一般的な遅延ストリームを理解している人が素直に想像するとおりの動作をします。一方、Lazy sequenceには気をつけるべき「癖」があります (マニュアルに書いてある通りです)。
  • Lazy sequenceはリストと統合されている: Lazy sequenceはGaucheのSchemeレベルでは通常のリストと(副作用を使うなどしなければ)区別がつきません。すなわち、リストを取るあらゆる関数にそのままLazy sequenceを渡すことができます。util.streamはストリームを扱う関数群が別個に用意されていて、あらゆる操作はその関数群を通して行うことになります。ストリームはリストとよく似ているにもかかわらず、stream-car, stream-cdr, stream-map, stream-takeなどリスト操作関数をそのまま反映した関数群を別々に使う必要があります。(Lazy sequenceにもlmap, ltakeなどの関数がありますが、これは「返す値がlazy sequenceになる」という区別であって、入力が通常のリストかLazy sequenceかという区別はありません)。マニュアルに挙げたmatchを使う例はそのひとつで、util.streamで同じことをしようとするとmatch関数のstream版を実装しなおさなければなりません。
  • Lazy sequenceは高速: Lazy sequenceはGaucheの言語コアでサポートされていて、さらにGaucheで効率良く動作するような仕様にしてあります (それが上で触れた「癖」です)。そのためutil.streamよりかなり高速に動作します。このことはまた、Lazy sequenceを他のScheme処理系に移植することは難しいことも意味します。Lazy sequenceを使ったコードを他のScheme処理系で動かすのは難しいでしょう。
編集 履歴 (1)
  • なるほど。移植を考えるならストリームを使い、移植を考えなくても良いなら、癖に注意しつつ遅延シーケンスを使えば良いということですね。 -
  • ありがとうございます。 -
ウォッチ

この質問への回答やコメントをメールでお知らせします。