(sicp51)put-getの修正と問題4.11〜4.12

以下のことに気づいたので修正

  • put/getの引数の順序はop, typeの順だった(前のはtype, op)
    • install-evalの部分を少し変える必要がありそう。*1
    • あと、evalのgetの部分も
    • あー、変えなくていいのか。(get 'eval 'if)とかはif形式のオブジェクトに対するevalと考えればいい。
  • hash-table-getはテーブルに値が存在しない場合の戻り値を引数で指定できる。
    • condの=>を使って結構綺麗に書ける気がした

put/get

(define-module sicp.put-get  (export put get))
(select-module sicp.put-get)

(define *parent-table* (make-hash-table 'equal?))

(define (put op type item)
  (cond ((hash-table-get *parent-table* op #f)
         => (cut hash-table-put! <> type item))
        (else
         (hash-table-put! *parent-table* op (make-hash-table 'equal?))
         (put op type item))))

(define (get op type)
  (cond ((hash-table-get *parent-table* op #f)
         => (cut hash-table-get <> type #f))
        (else #f)))

(provide "sicp/put-get")

問題4.12

以下の定義を共通部分を抜き出して抽象化するという問題。

(define (lookup-variable-value var env)
  (define (env-loop env)
    (define (scan vars vals)
      (cond ((null? vars)
             (env-loop (enclosing-environment env)))
            ((eq? var (car vars))
             (car vals))
            (else (scan (cdr vars) (cdr vals)))))
    (if (eq? env the-empty-environment)
        (error "Unbound variable" var)
        (let ((frame (first-frame env)))
          (scan (frame-variables frame)
                (frame-values frame)))))
  (env-loop env))

(define (set-variable-value! var val env)
  (define (env-loop env)
    (define (scan vars vals)
      (cond ((null? vars)
             (env-loop (enclosing-environment env)))
            ((eq? var (car vars))
             (set-car! vals val))
            (else (scan (cdr vars) (cdr vals)))))
    (if (eq? env the-empty-environment)
        (error "Unbound variable -- SET!" var)
        (let ((frame (first-frame env)))
          (scan (frame-variables frame)
                (frame-values frame)))))
  (env-loop env))

(define (define-variable! var val env)
  (let ((frame (first-frame env)))
    (define (scan vars vals)
      (cond ((null? vars)
             (add-binding-to-frame! var val frame))
            ((eq? var (car vars))
             (set-car! vals val))
            (else (scan (cdr vars) (cdr vals)))))
    (scan (frame-variables frame)
          (frame-values frame))))

回答(やりすぎた)

(define (env-scan-maker var fail found)
  (define (scan vars vals)
    (cond ((null? vars) (fail))
          ((eq? var (car vars)) (found vals))
          (else
           (scan (cdr vars) (cdr vals)))))
  scan)

(define (env-loop-maker found-f error-f)
  (lambda (var env)
    (let env-loop ((env env))
      (define scan (env-scan-maker var
                                   (cute env-loop (enclosing-environment env))
                                   found-f))
        (env-pretravarse-check scan env error-f))))

(define (travarse-dispatcher fn env)
  (let ((frame (first-frame env)))
    (fn (frame-variables frame)
        (frame-values frame))))

(define (env-pretravarse-check scan-f env error-f)
  (if (eq? env the-empty-environment)
      (error-f)
      (travarse-dispatcher scan-f env)))

(define (lookup-variable-value var env)
  ((env-loop-maker (lambda (vals) (car vals))
                  (cut error "Unbound variable" var))
   var env))

(define (set-variable-value! var val env)
  ((env-loop-maker (lambda (vals) (set-car! vals val))
                   (cut error "Unbound variable -- SET!" var))
   var env))

(define (define-variable! var val env)
  (let1 frame (first-frame env)
    (define scan (env-scan-maker var
                  (cut add-binding-to-frame! var val frame)
                  (lambda  (vals) (set-car! vals val))))
    (scan (frame-variables frame) (frame-values frame))))

4.11と4.12の複合

(var . value)というpairで格納するとassocが使えるので少し楽に書ける。
ただ、add-binding-frameが少しかきづらかった気がした。

(define the-empty-environment '('env ))

(define (make-frame variables values)
  (map cons variables values))
(define (frame-variables frame) (map car frame))
(define (frame-values frame) (map cdr frame))

(define (add-binding-to-frame! var val frame)
  (let1 new (cons var val)
    (set-cdr! frame (cons new (cdr frame)))))

(define (env-scan-maker var fail found)
  (define (scan frame)
    (cond ((asoc var frame) => (cut found <>))
          (else (fail))))
  scan)

(define (env-loop-maker found error-f)
  (lambda (var env)
    (let env-loop ((env env))
      (define scan (env-scan-maker var
                                   (cute env-loop (enclosing-environment env)) 
                                   found))
      (if (eq? env the-empty-environment)
          (error-f)
          (scan (first-frame env))))))


(define (lookup-variable-value var env)
  ((env-loop-maker  (lambda (pair) (cdr pair))
                    (cut error "Unbound variable" var))
   var env))

(define (set-variable-value! var value env)
  ((env-loop-maker  (lambda (pair) (set-cdr! pair value))
                    (cut error "Unbound variable -- SET!" var))
   var env))

(define (define-variable! var value env)
  (define scan
    (env-scan-maker var
                    (cut add-binding-to-frame! var val frame)
                    (lambda (pair) (set-cdr! pair value))))
    (scan (first-frame env)))

*1:define-action!を元に定義していたので変える場所は一ヶ所で済む!!