プログラミング再入門

プログラミングをもう一度ちゃんと勉強する読書ノート

SICP 3.5.5 Modularity of Functional Programs and Modularity of Objects

ノート

関数型プログラミングのモジュラリティとオブジェクトのモジュラリティ。
モジュラリティはモジュール性。モジュール性は部品化度合いかな。

乱数列をストリームにすると、チェザロの実験(単に乱数列の隣り合う二つが互いに素か否かを判定する)とモンテカルロ法(単に実験の成功率を順に計算してストリームとして出力する)の組み合わせで、何回実験するかは更に上位に委ねる事が出来る。

テキストのpiの定義を動作させてみる。

> (stream->list pi 10)
'(2.449489742783178
  3.4641016151377544
  3
  2.8284271247461903
  2.7386127875258306
  3
  3.24037034920393
  3.0983866769659336
  3
  2.9277002188455996)
> (stream-ref pi 100)
3.101458950082625
> (stream-ref pi 1000)
3.1326866249588514
> (stream-ref pi 10000)
3.143112639230578
> 
Exercise 3.81

乱数発生器にリクエストのストリームを取る様にする。まぁリクエストは'generateか'resetと種からなるペアのどちらかからなるストリームとする。要は入力ストリームと既に発生した乱数のストリームを同時に走査してrand-updateに与える引数を決める。

  • generateと言われれば乱数のストリームから値を取り
  • resetと言われればリクエストされた値を種にする。

最初の乱数の種もリクエストが'resetであればそこから、'generateであればrandom-initとする。

(define (random-stream requests)
  (define (rand req prev)
    (rand-update (cond ((and (symbol? req) (eq? req 'generate)) prev)
                       ((and (pair? req) (eq? (car req) 'reset)) (cdr req))
                       (else (error "unknown request - " req)))))
  (define random-numbers
    (cons-stream (rand (stream-car requests) random-init)
                 (stream-map rand (stream-cdr requests) random-numbers)))
  random-numbers)

(define input-stream (cons-stream (cons 'reset 123)
                                  (cons-stream 'generate
                                               (cons-stream 'generate
                                                            (cons-stream (cons 'reset 123)
                                                                         (cons-stream 'generate
                                                                                      (cons-stream 'generate '())))))))

実行結果

> (stream->list (random-stream input-stream) 6)
'(602631724 1412780618 3434508048 602631724 1412780618 3434508048)
> 

同じ乱数を2回生成している。

Exercise 3.82

Exercise 3.5の時と同様に小数の乱数を得る為にRacketのrandomを使用する。
monte-carloは合否判定した結果のストリームと合格・不合格数の初期値を引数に取るので、二つの乱数のストリームに対してPをmapして合否判定した結果のストリームを得る。
estimate-integralは実際の面積に直す為にmonte-carloの結果に(x1, y1)〜(x2, y2)を対角線とする四角形の面積を掛ける。monte-carloはストリームを返すのでこの掛け算もmapする。
estimate-piは関数ではなくストリームになる。

(define (random-numbers-in-range low high)
  (define (rand)
    (let ((range (- high low)))
      (+ low (* (random) range))))
  (cons-stream (rand) (random-numbers-in-range low high)))

(define (estimate-integral P x1 x2 y1 y2)
  (stream-map (lambda (i) (* (* (- x2 x1) (- y2 y1)) i 1.0))
              (monte-carlo (stream-map (lambda (x y) (P x y))
                                       (random-numbers-in-range x1 x2)
                                       (random-numbers-in-range y1 y2))
                           0 0)))

(define (in-unit-circle x y)
  (< (sqrt (+ (* x x) (* y y))) 1.0))

(define estimate-pi
  (estimate-integral in-unit-circle -1 1 -1 1))

random-numbers-in-rangeの動作確認

> (stream->list (random-numbers-in-range 3 5) 10)
'(4.224769805733143
  4.421731196278727
  4.7390242181059525
  3.1973619645115194
  4.561843632455793
  4.923699719395848
  3.0980561437075207
  4.156163424365687
  3.094264130482203
  3.602501941220929)
> 

実行結果

> (stream-ref estimate-pi 100)
3.089108910891089
> (stream-ref estimate-pi 1000)
3.104895104895105
> (stream-ref estimate-pi 10000)
3.118088191180882
> (stream-ref estimate-pi 100000)
3.142128578714213
> (stream-ref estimate-pi 1000000)
3.1435488564511433
>
A functional-programming view of time

関数型プログラミングから見た『時間』。
時間とともに変化する現実世界をシミュレートする為にローカル変数や代入を導入した。
時間とともに変化する値をストリームで表現する事で『時間』をプログラムから分離する事が出来た。

オペレータがタイプする金額をストリームに出来たら、残高の初期値と引き出す額のストリームから残高のストリームが定義出来、これは完全に関数として記述出来る。

プログラムに時間を持ち込んでいるのは、時間の世界にいるオペレータ。
時間とともに変化する実世界を時間の概念を含んだままモデル化するのは直感的で分かりやすいが、色んな面倒な問題を引き起こす。

かと言って、関数型にすれば時間の概念から来る問題の全てが消える訳ではない。二つのプロセスからアクセスされるオブジェクトの場合、二つの入力をマージする必要があるがそのマージの方法は簡単ではない。