問題4.50-4.51

amb評価器をいじります。

4.50

rambを作ります。まずanalyzeに次を追加。

...
((ramb? exp) (analyze-ramb exp))
...

こんなのも作ります。

(define (ramb? exp) (tagged-list? exp 'ramb))
(define (ramb-choices exp) (cdr exp))

最後にanalyze-rambをanalyze-ambを参考に作ります。

(define (analyze-ramb exp)
  (let ((cprocs (map analyze (ramb-choices exp))))
    (lambda (env succeed fail)
      (define (del-list-item lst n)
        (cond ((null? lst) '())
              ((= n 0) (cdr lst))
              (else (cons (car lst)
                          (del-list-item (cdr lst) (- n 1))))))
      (define (try-next choices)
        (if (null? choices)
            (fail)
            (let* ((rand-num (random (length choices)))
                   (cproc (list-ref choices rand-num)))
              (cproc env
                     succeed
                     (lambda ()
                       (try-next (del-list-item choices rand-num)))))))
      (try-next cprocs))))

実行します。

;;; Amb-Eval input:
(ramb 1 2 3 4)
;;; Starting a new problem 
;;; Amb-Eval value:
2

;;; Amb-Eval input:
try-again
;;; Amb-Eval value:
3

;;; Amb-Eval input:
try-again
;;; Amb-Eval value:
1

;;; Amb-Eval input:
try-again
;;; Amb-Eval value:
4

;;; Amb-Eval input:
try-again
;;; There are no more values of
(ramb 1 2 3 4)

できました。4.49のAlyssaの問題はrambを使うと次のようになります。

;;; Amb-Eval input:
(parse '(the big cat sleeps with a student))
;;; Starting a new problem 
;;; Amb-Eval value:
(sentence (simple-noun-phrase (article the) (adj-phrase (adj thin) (noun cat))) (verb-phrase (verb lectures) (prep-phrase (prep by) (simple-noun-phrase (article a) (noun student)))))

;;; Amb-Eval input:
try-again
;;; Amb-Eval value:
(sentence (simple-noun-phrase (article the) (adj-phrase (adj thin) (noun cat))) (verb-phrase (verb lectures) (prep-phrase (prep by) (simple-noun-phrase (article a) (noun class)))))

parseによる生成文はランダムですが、try-againすると前の方がいっしょになりますね。縦型探索だから仕方ないのかな。まあrambできたしこれでいいや。

4.51

失敗してももとに戻らない代入は確かに便利ですね。どれくらい探索してるのかとか調べられるんじゃないでしょうか。
analyzeに以下を追加。

...
((permanent-assignment? exp)
 (analyze-permanent-assignment exp))
...

述語とかを書く。

(define (permanent-assignment? exp)
  (tagged-list? exp 'permanent-set!))
(define (permanent-assignment-variable exp) (cadr exp))
(define (parmanent-assignment-value exp) (caddr exp))
(define (make-permanent-assignment var val)
  (list 'permanent-set! var val))

analyze-assignmentを参考にanalyze-permanent-assignmentを書きます。ちなみに、analyze-sequenceとanalyze-assignmentですが教科書のだと動かないので下のようにしています。

(define (analyze-sequence exps)
  (define (sequentially a b)
    (lambda (env succeed fail)
      (a env
         (lambda (a-value fail2)
           (b env succeed fail2))
         fail)))
  (define (loop first-proc rest-procs)
    (if (null? rest-procs)
        first-proc
        (loop (sequentially first-proc (car rest-procs))
              (cdr rest-procs))))
  (let ((procs (map analyze (scan-out-defines exps))))
    (if (null? procs)
        (error "Empty sequence -- ANALYZE"))
    (loop (car procs) (cdr procs))))
(define (analyze-assignment exp)
  (define (get-variable-value var env)
    (define (env-loop env)
      (scan-frame env
                  var
                  (lambda (x) (env-loop (enclosing-environment x)))
                  (lambda (x) (car x))))
    (env-loop env))
  (let ((var (assignment-variable exp))
        (vproc (analyze (assignment-value exp))))
    (lambda (env succeed fail)
      (vproc env
             (lambda (val fail2)
               (let ((old-value
                      (get-variable-value var env))); if you use lookup-variable-value, get error.
                 (set-variable-value! var val env)
                 (succeed 'ok
                          (lambda ()
                            (set-variable-value! var
                                                 old-value
                                                 env)
                            (fail2)))))
             fail))))

make-procedureでscan-out-definesを使おうとすると、既に実行手続き形式になっているのでうまく行きません。なので、まだ生の式の状態であるanalyze-sequenceの中でscan-out-definesを使っています。
また、old-valueを得るときにlookup-variable-valueを使おうとすると、scan-out-definesのせいで変数が'*unassigned*に束縛されているのでエラーになってしまいます。代わりにエラー判定を行わないget-variable-valueを使っています。
これがちゃんと全ての要件を満たしているかどうかは未確認です。とりあえず今のところちゃんと動きはします。

じゃあanalyze-permanent-assignmentを作ります。値を戻す処理を省けばいいです。

(define (analyze-permanent-assignment exp)
  (let ((var (permanent-assignment-variable exp))
        (vproc (analyze (permanent-assignment-value exp))))
    (lambda (env succeed fail)
      (vproc env
             (lambda (val fail2)
               (set-variable-value! var val env)
               (succeed 'ok fail2))
             fail))))

確認します。

;;; Amb-Eval input:
(define count 0)
;;; Starting a new problem 
;;; Amb-Eval value:
ok

;;; Amb-Eval input:
(let ((x (an-element-of '(a b c)))
      (y (an-element-of '(a b c))))
  (permanent-set! count (+ count 1))
  (require (not (eq? x y)))
  (list x y count))
;;; Starting a new problem 
;;; Amb-Eval value:
(a b 2)

;;; Amb-Eval input:
try-again
;;; Amb-Eval value:
(a c 3)

;;; Amb-Eval input:
(set! count 0)
;;; Starting a new problem 
;;; Amb-Eval value:
ok

;;; Amb-Eval input:
(let ((x (an-element-of '(a b c)))
      (y (an-element-of '(a b c))))
  (set! count (+ count 1))
  (require (not (eq? x y)))
  (list x y count))
;;; Starting a new problem 
;;; Amb-Eval value:
(a b 1)

;;; Amb-Eval input:
try-again
;;; Amb-Eval value:
(a c 1)
4.50の補足

ところでan-element-ofを見て思ったんですが、昨日の4.49はan-element-ofを使うとすごく簡単に書けたような気がします。
今日やった4.50もリストからrandomに要素を取ってくるan-random-element-ofみたいなのを書けばできるような気がします。でもこの場合単にambをrambにすると、抜き出してくる要素がリストの左から右に飛び飛びの値、そのあと右から左に順に取ってくる、みたいな動きになってしまいます。つまり、'(1 2 3 4)だったら[1,3,2,4]みたいな順に抜き出すことができません。
なので与えられたリストの順をrandomに並べたものをan-element-ofで抜き出してくればいいように思います。そう書いてみると、

(define (an-random-element-of lst)
  (define (del-list-item lst n)
    (cond ((null? lst) '())
          ((= n 0) (cdr lst))
          (else (cons (car lst)
                      (del-list-item (cdr lst) (- n 1))))))
  (define (random-mix-list lst)
    (if (null? lst)
        '()
        (let ((rand-num (random (length lst))))
        (cons (list-ref lst rand-num)
              (random-mix-list (del-list-item lst rand-num))))))
  (let rec ((rand-list (random-mix-list lst)))
    (require (not (null? rand-list)))
    (amb (car rand-list) (rec (cdr rand-list)))))

(define (parse-word word-list)
  (require (not (null? *unparsed*)))
  (require (memq (car *unparsed*) (cdr word-list)))
  (set! *unparsed* (cdr *unparsed*))
  (list (car word-list)
        (an-random-element-of (cdr word-list))))

こんな風に書けると思います。an-random-element-ofを実行すると、

;;; Amb-Eval input:
(an-random-element-of '(1 2 3 4 5 6 7 8))
;;; Starting a new problem 
;;; Amb-Eval value:
3

;;; Amb-Eval input:
try-again
;;; Amb-Eval value:
8

;;; Amb-Eval input:
try-again
;;; Amb-Eval value:
7

;;; Amb-Eval input:
try-again
;;; Amb-Eval value:
2

;;; Amb-Eval input:
try-again
;;; Amb-Eval value:
6

;;; Amb-Eval input:
try-again
;;; Amb-Eval value:
5

;;; Amb-Eval input:
try-again
;;; Amb-Eval value:
1

;;; Amb-Eval input:
try-again
;;; Amb-Eval value:
4

;;; Amb-Eval input:
try-again
;;; There are no more values of
(an-random-element-of '(1 2 3 4 5 6 7 8))

とrandomにリストの要素を全部取り出してくれます。parseすると、

;;; Amb-Eval input:
(parse '(the cat eats in the class))
;;; Starting a new problem 
;;; Amb-Eval value:
(sentence (simple-noun-phrase (article a) (noun cat)) (verb-phrase (verb eats) (prep-phrase (prep by) (simple-noun-phrase (article a) (noun professor)))))

;;; Amb-Eval input:
try-again
;;; Amb-Eval value:
(sentence (simple-noun-phrase (article a) (noun cat)) (verb-phrase (verb eats) (prep-phrase (prep by) (simple-noun-phrase (article a) (noun student)))))

;;; Amb-Eval input:
try-again
;;; Amb-Eval value:
(sentence (simple-noun-phrase (article a) (noun cat)) (verb-phrase (verb eats) (prep-phrase (prep by) (simple-noun-phrase (article a) (noun cat)))))

で上の方で作った4.50のものと同じ性能になっています。


眠いのでここまで。