2012年5月9日水曜日

Scheme第18歩


さて、Scheme学習シリーズもそろそろ延べ3週間なので、ぼちぼち締めに入ろう。最後は遅延評価の話。

多くのプログラミング言語は、基本的に前から順にプログラムを実行していく。Schemeも例外ではなく、例えば
(clip x (+ y 10) z))
という記述があったら(そしてclipがスペシャルフォームではない通常の関数であったら)、

  1. xを評価
  2. yを評価
  3. 10を評価
  4. y, 10を引数として+を呼び出し((+ y 10)を評価)
  5. zを評価
  6. x, (+ y 10), zを引数としてclipを呼び出し((clip x (+ y 10) z)を評価)

という順番で評価していく。

ここで、例えばclipが以下のように定義されているとする。
(define clip (lambda (min max v)
  (cond
    ((<= v min) min)
    ((>= v max) max)
    (else v)
  )
))
まあ、何の変哲もないクリッピングなのだが、ここで前出の
(clip x (+ y 10) z))
をx=0, y=100, z=-10として評価することを考えてみる。すると、condの最初の条件が早々に満たされるので、maxの値(+ y 10)は折角計算したにも関わらず、結果的には使われない。

こんなとき、値が必要になるまで式の評価を後回しにしようというのが遅延評価の考え方。必要にならなければ無期限後回しとなるので、結果として無駄な計算を行わずに済むというわけ。

Schemeで遅延評価を行うには、delayとforceを使う。
(define x 100)
(define p (delay (+ x 1))
(set! x 1000)
(print (force p)) ; 1001
上の例では(+ x 1)の評価をdelayで後回しにして、printする際にforceで評価させている。この例のpのようにdelayで遅らせた式を表現するオブジェクトのことを、Schemeではプロミスと呼ぶ。
注意すべきは、プロミスは評価値をキャッシュしている点。上記のコードに続いて
(set! x 2000)
(print (force p)) ; 1001
と実行しても、既に1度forceされているプロミスpは、自身の評価値が1001であると記憶しているので、改めて(+ x 1)を評価して2001となることはない。

なお、プロミスではない式をforceすると、単純にその場でその式を評価したのと同じ値となる。よって、
(let ((p xx)) (if (promiss? p) (force p) p))
のようにプロミスかどうかを判定せずとも
(force xx)
でよい。

…締め切れてないので、もうちょっと続く。

0 件のコメント:

コメントを投稿