問題4.25-4.31

遅延評価のSchemeについてです。

4.25

> (define (unless condition usual-value exceptional-value)
    (if condition usual-value exceptional-value))
> (define (factorial n)
    (unless (= n 1)
      (* n (factorial (- n 1)))
      1))
> (factorial 5)

これは終わりません。unlessは(* n (factorial (- n 1)))を評価しようとします。一見n=0の時に終わりそうですが、この時でも作用的順序の規則から(* n (factorial (- n 1)))を評価しなければならないので、いつまでたっても評価が終わらないということになります。
正規順序の評価の下では、これはちゃんと動きます。

4.26

unlessの実装は、単にifにして第二、第三引数を入れ替えればいいので省略します。
unlessが手続きであれば有用であるというのは、やはり高階手続きとして利用する時でしょうか。でも有用な場合と言われて思いつくものがないです。lambdaでラップすれば大抵の場合に使えるし。分からない。

4.27

遅延評価器を作りました。遅延評価器での次のセッション(この言葉使ってみたかった)を考えます。

;;; L-Eval input:
(define count 0)
;;; L-Eval value:
ok

;;; L-Eval input:
(define (id x)
  (set! count (+ count 1))
  x)
;;; L-Eval value:
ok

;;; L-Eval input:
(define w (id (id 10)))
;;; L-Eval value:
ok

;;; L-Eval input:  ...(*1)
count
;;; L-Eval value:
1

;;; L-Eval input:  ...(*2)
w
;;; L-Eval value:
10

;;; L-Eval input:  ...(*3)
count
;;; L-Eval value:
2

メモ化によるものかな、と思いましたが、ググってみるとどうやら違うようです。
wのdefine時に外側のidが評価されるのでcountが1になり、w評価時に内側のidが評価されるのでcountが2になる、ということらしいです。次の例からも、どうやらそのようです。

;;; L-Eval input:
(define w (id (id (id (id 10)))))
;;; L-Eval value:
ok

;;; L-Eval input:
count
;;; L-Eval value:
1

;;; L-Eval input:
w
;;; L-Eval value:
10

;;; L-Eval input:
count
;;; L-Eval value:
4

;;; L-Eval input:
w
;;; L-Eval value:
10

;;; L-Eval input:
count
;;; L-Eval value:
4

定義時は確かに値が必要ないからthunkが返ってくるだけなんですね。で、評価時にthunkを値が出るまで掘ると。w評価後にwに値が対応づけられるということですね。複雑だなぁ。

4.28

evalしてthunkが返ってきた場合がだめで、thunkが返ってくるのは遅延評価の引数の場合。だから引数に手続きがあるような手続きを定義するとだめなはず。

;;; L-Eval input:
(define (map f lst)
  (if (null? lst)
      '()
      (cons (f (car lst)) (map f (cdr lst)))))
;;; L-Eval value:
ok

;;; L-Eval input:
(map (lambda (x) x) '(1 2 3 4 5))
. Unknown procedure type -- APPLY (thunk (lambda (x) x) #0=(((map #f #t false true car cdr cons null? list + - * / modulo = < > eq? not display newline) (procedure (f lst) ((if (null? lst) (quote ()) (cons (f (car lst)) (map f (cdr lst))))) #0#) #f #t #f #t (primitive #<primitive:car>) (primitive #<primitive:cdr>) (primitive #<primitive:cons>) (primitive #<primitive:null?>) (primitive #<primitive:list>) (primitive #<primitive:+>) (primitive #<primitive:->) (primitive #<primitive:*>) (primitive #<primitive:/>) (primitive #<primitive:modulo>) (primitive #<primitive:=>) (primitive #<primitive:<>) (primitive #<primitive:>>) (primitive #<primitive:eq?>) (primitive #<primitive:not>) (primitive #<primitive:display>) (primitive #<primitive:newline>))))

ということで、ダメですね。正規順序評価だからくそ長いエラーが出てきます。actual-valueを使うとちゃんとできます。

;;; L-Eval input:
(define (map f lst)
  (if (null? lst)
      '()
      (cons (f (car lst)) (map f (cdr lst)))))
;;; L-Eval value:
ok

;;; L-Eval input:
(map (lambda (x) x) '(1 2 3 4 5))
;;; L-Eval value:
(1 2 3 4 5)

4.29

同じオブジェクトを何度も使いまわす場合遅くなるはず。だからこういう場合はメモ化を外すとかなり遅くなるはず。

;;; L-Eval input:
(define (f n)
  (if (= n 0)
      0
      (+ n (f (- n 1)))))
;;; L-Eval value:
ok

;;; L-Eval input:
(f (f 100))

実際遅くなりました。次に、メモ化時と非メモ化時での応答の違いを見ます。countとidは4.27と同じ。
まず非メモ化時。

;;; L-Eval input:
(define (square x) (* x x))
;;; L-Eval value:
ok

;;; L-Eval input:
(square (id 10))
;;; L-Eval value:
100

;;; L-Eval input:
count
;;; L-Eval value:
2

メモ化時。

;;; L-Eval input:
(define (square x) (* x x))
;;; L-Eval value:
ok

;;; L-Eval input:
(square (id 10))
;;; L-Eval value:
100

;;; L-Eval input:
count
;;; L-Eval value:
1

squareが(* (id 10) (id 10))に展開されるので、メモ化版だと(id 10)がevaluated-thunkになるから(set! count (+ count 1))が評価されないってことですかね。たぶん。

4.30

うーん、遅延評価苦手かも。

a.

newlineもdisplayも基本手続きで、遅延評価されないから。

b.

元々のeval-sequenceでの評価。

;;; L-Eval input:
(define (p1 x)
  (set! x (cons x '(2)))
  x)
;;; L-Eval value:
ok

;;; L-Eval input:
(define (p2 x)
  (define (p e)
    e
    x)
  (p (set! x (cons x '(2)))))
;;; L-Eval value:
ok

;;; L-Eval input:
(p1 1)
;;; L-Eval value:
(1 2)

;;; L-Eval input:
(p2 1)
;;; L-Eval value:
1

Cyなんとかのeval-sequenceによる評価。

;;; L-Eval input:
(p1 1)
;;; L-Eval value:
(1 2)

;;; L-Eval input:
(p2 1)
;;; L-Eval value:
(1 2)
c.

actual-valueの中のforce-itは、遅延オブジェクト以外はそのまま返すから。

d.

今までのSchemeと同じように書きたいとしたら、Cyの指摘は正しいんじゃないかな。

4.31

え、嫌。やりたくない…。省略。


だんだん難しくなってきました。4章が大変って聞いてたけど大変ですね。やっぱり。とりあえず今日はここまで。