[ 目次, 前節, 次節, 索引 ]

4.2.3 遅延評価リストとしてのストリーム



3.5.1節でストリームを遅延リストとしてどう実装するか示した. ストリームのcdrを計算する「約束」を構成し, その約束を後で実行する特殊形式delaycons-streamを導入した. 評価プロセスに新しい制御が必要になった時, 特殊形式を導入するという一般的技法を使うことは出来るが, これは不便である. 一つには特殊形式は手続きのような第一級オブジェクトではなく, 高階手続きと一緒に使うことが出来ない.39 その上リストに似ているが非なる新しい種類のデータオブジェクトとしてストリームを作り出さなければならず, ストリームに使うため, 通常のリスト演算(map, appendなど) を再実装しなければならなかった.

   遅延評価ではストリームとリストは同一になる. 特殊形式もリストとストリームの別々の演算も不要になる. 唯一なすべきは, consがノンストリクトであるように準備することである. これを実現する方法の一つは遅延評価器を拡張し, ノンストリクトな基本手続きを許すようにし, consをそういうものとして実装することだ. 更に簡単なのは, consを基本手続きとして実装するという基本的な必要は全くないということを思い出すことである(2.1.3節). そうではなく, 対は手続きで表現出来る:40


(define (cons x y)
  (lambda (m) (m x y)))


(define (car z)
  (z (lambda (p q) p)))


(define (cdr z)
  (z (lambda (p q) q)))

   これらの基本演算を使うと, リスト演算の標準の定義は有限なのと同時に無限リスト(ストリーム)に対しても働くようになり, ストリーム演算はリスト演算として実装される. 例をいくつか示す:


(define (list-ref items n)
  (if (= n 0)
      (car items)
      (list-ref (cdr items) (- n 1))))


(define (map proc items)
  (if (null? items)
      '()
      (cons (proc (car items))
            (map proc (cdr items)))))


(define (scale-list items factor)
  (map (lambda (x) (* x factor))
       items))


(define (add-lists list1 list2)
  (cond ((null? list1) list2)
        ((null? list2) list1)
        (else (cons (+ (car list1) (car list2))
                    (add-lists (cdr list1) (cdr list2))))))


(define ones (cons 1 ones))


(define integers (cons 1 (add-lists ones integers)))

;;; L-Eval input:
(list-ref integers 17)
;;; L-Eval value:
18

   これらの遅延リストは3章のストリームより更に遅延度が高いことに注意しよう: リストのcarcdrと同様に遅延出来る.41 実際, 遅延対のcarcdrへのアクセスもリスト要素の値を強制する必要がない. 値はそれが真に必要になった時---例えば基本手続きの引数として使うとか, 答として印字する時---にだけ強制される.

   遅延対は3.5.4節の, ストリームと共に生じる問題にも役立つ. ループを持つシステムのストリームモデルの形式化では, プログラムはcons-streamによるものだけでなく, 積極的に delay演算を撒き散す必要があることが分った. 遅延評価では手続きへのすべての引数は一様に遅延される. 例えば3.5.4節で初めに意図したようなリストを積分し, 微分方程式を解く手続きが実装出来る:


(define (integral integrand initial-value dt)
  (define int
    (cons initial-value
          (add-lists (scale-list integrand dt)
                    int)))
  int)


(define (solve f y0 dt)
  (define y (integral dy y0 dt))
  (define dy (map f y))
  y)

;;; L-Eval input:
(list-ref (solve (lambda (x) x) 1 0.001) 1000)
;;; L-Eval value:
2.716924

問題 4.32


3章のストリームと本節に述べた「遅延度の高い」遅延リストの間の違いを示す例をいくつかあげよ. この余分な遅延度はどう利用出来るか.

問題 4.33


Ben Bitdiddleは上の遅延リストの実装を式

(car '(a b c))

を使ってテストした. 驚いたことにエラーになった. しばらく考えた後, クォート式を読み込んで得た「リスト」はcons, carおよびcdrの新しい定義で操作されるリストとは違うことに気がついた. 評価器のクォート式の扱いを修正し, 駆動ループで入力するクォートしたリストが, 真の遅延リストを生じるようにせよ.

問題 4.34


評価器の駆動ループを修正し, 遅延対とリストが正当に印字出来るようにせよ. (無限リストに対してどうするか.) 評価器がそれを印字するために認識出来るよう, 遅延対の表現も修正する必要がある.



39 これはまさに問題4.26 にあったunless手続きの論点に他ならない.

40 これは問題2.4で述べた手続き表現である. 本質的にどういう手続き表現でも(例えばメッセージパッシング実装でも)構わない. この定義を駆動ループで遅延評価器に入力すれば, 実装出来ることに注意しよう. cons, carcdrを大域環境で基本的手続きとして初めから入れておいても, それらは再定義されるかもしれない. (問題4.33および4.34も参照)

41 そのため, 並びだけでなく, 更に一般的なリスト構造の遅延版を作ることが出来る. Hughes 1990は 「遅延木」の応用のいくつかを論じる.

[ 目次, 前節, 次節, 索引 ]