遅延評価ではストリームとリストは同一になる. 特殊形式もリストとストリームの別々の演算も不要になる. 唯一なすべきは, 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章のストリームより更に遅延度が高いことに注意しよう: リストのcarもcdrと同様に遅延出来る.41 実際, 遅延対のcar やcdrへのアクセスもリスト要素の値を強制する必要がない. 値はそれが真に必要になった時---例えば基本手続きの引数として使うとか, 答として印字する時---にだけ強制される.
遅延対は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
39
これはまさに問題4.26
にあったunless手続きの論点に他ならない.
40
これは問題2.4で述べた手続き表現である. 本質的にどういう手続き表現でも(例えばメッセージパッシング実装でも)構わない. この定義を駆動ループで遅延評価器に入力すれば, 実装出来ることに注意しよう. cons, carやcdrを大域環境で基本的手続きとして初めから入れておいても, それらは再定義されるかもしれない. (問題4.33および4.34も参照)
41
そのため,
並びだけでなく, 更に一般的なリスト構造の遅延版を作ることが出来る.
Hughes 1990は
「遅延木」の応用のいくつかを論じる.