SICPの3.5 Streamsを最初からバリバリ読んで、Exercise 3.52まで解くことにしましょう。

という訳で、この節では考え方を変えて
ということから、このモデルの状態の並びのようなデータのストリームを中心に色々と物事を考えていくようです。
ここではストリームが何なのかを説明する為に2.2.3節で書いたリスト処理版のコードをストリーム版に置き換えています。
ストリーム版のコードは
cons を cons-stream にcar を stream-car にcdr を stream-cdr にのようにペアを扱うAPIを置き換えただけです。
胆は
cons-stream は特殊形式で、 (cons-stream a b) の b はすぐには評価されないstream-cdr された時点で初めて b が評価されるという点です。
ストリームの後続の要素を辿った時に初めて必要な計算が行われるので、
これでリスト処理のように簡潔なコードを保ちつつも、
リスト処理のように中間結果のリストを作ることが無くなるので効率良く処理ができます。
cons-stream と stream-cdr は特殊なもののように見えますが、
cons-stream と stream-cdr は delay と force があれば実装できるdelay は実のところ lambda で実現できるforce は delay で作った手続きを呼ぶだけに過ぎないという訳で簡単に実装できます。
ただしテキストだとSchemeでの特殊形式の定義方法は載ってないので、そこは自力で調べる必要はあります。
例えば以下のようにして定義できます:
(define-syntax delay
(syntax-rules ()
((_ <exp>)
(memo-proc (lambda () <exp>)))))Complete the following definition, which generalizes stream-map to allow
procedures that take multiple arguments, analogous to map in section 2.2.3,
footnote 12.
(define (stream-map proc . argstreams)
(if (<??> (car argstreams))
the-empty-stream
(<??>
(apply proc (map <??> argstreams))
(apply stream-map
(cons proc (map <??> argstreams))))))このテンプレートを正直に埋めると以下のコードになるんですけど:
(define (stream-map proc . argstreams)
(if (stream-null? (car argstreams))
the-empty-stream
(cons-stream
(apply proc (map stream-car argstreams))
(apply stream-map
(cons proc (map stream-cdr argstreams))))))例えば (stream-map proc a b c) という呼び出しがあった場合、a の要素数が b や c より少なかった場合は問題ないのですが、b や c の方が少なかった場合はエラーになってしまいますね。
という訳で、ちゃんと直すと以下のコードになると思います:
(define (stream-map proc . argstreams)
(if (any stream-null? argstreams)
the-empty-stream
(cons-stream
(apply proc (map stream-car argstreams))
(apply stream-map
(cons proc (map stream-cdr argstreams))))))In order to take a closer look at delayed evaluation, we will use the
following procedure, which simply returns its argument after printing it:
(define (show x)
(display-line x)
x)What does the interpreter print in response to evaluating each expression in
the following sequence? [59]
(define x (stream-map show (stream-enumerate-interval 0 10)))
(stream-ref x 5)
(stream-ref x 7)(define ...) で 0(stream-ref x 5) で 1 2 3 4 5(stream-ref x 7) で 6 7が出力されますね。
stream-map はストリームの最初の要素については即座に処理するので、まず0が出力されるstream-ref はストリームの先頭から指定個数の要素を辿るが、 stream-map による元の要素の「変換」結果はメモ化されているので、既に評価済みの要素に対して show が再実行されることはない。ということから上記のような出力になります。
Consider the sequence of expressions
(define sum 0)
(define (accum x)
(set! sum (+ x sum))
sum)
(define seq (stream-map accum (stream-enumerate-interval 1 20)))
(define y (stream-filter even? seq))
(define z (stream-filter (lambda (x) (= (remainder x 5) 0))
seq))
(stream-ref y 7)
(display-stream z)これだと分かり辛いので以下のように各式に名前を付けてましょう:
(define sum 0)
(define (accum x) ; E0
(set! sum (+ x sum))
sum)
(print "E0: " sum ";")
(define seq (stream-map accum (stream-enumerate-interval 1 20))) ; E1
(print "E1: " sum ";")
(define y (stream-filter even? seq)) ; E2
(print "E2: " sum ";")
(define z (stream-filter (lambda (x) (= (remainder x 5) 0)) ; E3
seq))
(print "E3: " sum ";")
(stream-ref y 7) ; E4
(print "E4: " sum ";")
(display-stream z) ; E5
(print "E5: " sum ";")また、 base = (stream-enumerate-interval 1 20) とすると、
各シーケンスの内容は以下の通りです:
base = 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
seq = 1 3 6 10 15 21 28 36 45 55 66 78 91 105 120 136 153 171 190 210
y = 6 10 28 36 66 78 120 136 190 210
z = 10 15 45 55 120 190 210What is the value of sum after each of the above expressions is evaluated?
seq はまだ処理されていないからです。stream-map により seq の最初の要素が即座に処理されるからです。stream-filter は条件に合致する要素が見つかるまで seq を辿るからです。y の(0から数え始めて)7番目の要素が得られるまで seq が辿られるからです。display-stream により seq が最後まで辿られるからです。What is the printed response to evaluating the
stream-refanddisplay-streamexpressions?
stream-ref: 136display-stream: 10, 15, 45, 55, 120, 190, 210になりますね。
Would these responses differ if we had implemented
(delay <exp>)simply as(lambda () <exp>)without using the optimization provided bymemo-proc?
Explain.
当然結果は変わりますね。
メモ化されていなければseq に対して stream-cdr = 列挙される度に accum が呼ばれることになります。
そして seq はE2-E5で四重に列挙されるからです。
多分、
seq は列挙されてない)seq 定義時の stream-map により1が accum される)y 定義時の seq の列挙で base = 2-3 まで列挙される)z 定義時の seq の列挙で base = 2-4 が列挙される。ただし seq の戻り値は accum の結果なので、E2時点の結果6に2-4の総和を加えたものが z の条件に合う最初の要素になる)y の(0から数え始めて)7番目の要素が得られるまで seq が列挙される。 y の最初の要素の分で既に seq は base = 3 まで列挙済みなので、ここでは base = 4-17 までが列挙される)base = 5-20 が列挙される)ですね。なので
stream-ref: 162display-stream: 15, 180, 230, 305になります。
3.5.2 Infinite Streams
を読んで、その後は片っ端から問題を解いていきましょう。