A正規化 => β簡約 => 不要定義削除 - Scheme VM を書く
まずは理解を深めるために手で A正規化 => β簡約 => 不要定義削除をして見る。
もし間違いを見つけたらぜひご指摘ください_(__)_。(合っているかとても不安)
;; 元のコード あまりに都合の良いのは気のせい (let ([x 1]) (let ([a (let ([b x]) (+ b 1))]) a)) ;; A正規化 (let ([x 1]) (let ([b x]) (let ([a (+ b 1)]) a))) ;; β簡約 xに置き換える (let ([x 1]) (let ([b x]) (let ([a (+ x 1)]) a))) ;; β簡約 1に置き換える (let ([x 1]) (let ([b 1]) (let ([a (+ 1 1)]) a))) ;; 不要定義削除 (let ([a (+ 1 1)]) a) ;; 定数畳み込みとかやりすぎると 2
期待されるコード
実際にコードを書いてみたときの期待される動作は以下のようになる。
(eliminate (beta-reduction '(let ([x 1]) (let ([b x]) (let ([a (+ b 1)]) a))))) ;; => (let ((a (+ 1 1))) a)
β簡約
まず let のβ簡約のコードを書いてみよう。基本的には let が出てきたら (var val) の組を env に登録、body の中で var が参照されたら val に置き換えるというコード。 書いてて思ったのだけどA正規化後は let ではなくて let1 になっている方が断然扱いやすい。
(define (make-empty-env) (make-hash-table)) (define (env-exists? env key) (hash-table-exists? env key)) (define (env-get env key) (hash-table-get env key)) (define (env-put! env key val) (if (env-exists? env val) (hash-table-put! env key (env-get env val)) (hash-table-put! env key val))) (define (beta-reduction sexp) (define (reduction-let vars body env) (let loop ([vars vars] [next-vars '()]) (cond [(null? vars) `(let ,next-vars ,@(map (lambda (x) (reduction x env)) body))] [else (let ([var (caar vars)] [val (cadar vars)]) (unless (pair? val) (env-put! env var val)) (loop (cdr vars) (append next-vars (list (list var (reduction val env))))))]))) (define (reduction sexp env) (cond [(pair? sexp) (case (car sexp) [(let) (reduction-let (cadr sexp) (cddr sexp) env)] [else (map (lambda (x) (reduction x env)) sexp)])] [else (if (and (symbol? sexp) (env-exists? env sexp)) (env-get env sexp) sexp)])) (reduction sexp (make-empty-env))) (beta-reduction '(let ([x 1]) (let ([b x]) (let ([a (+ b 1)]) a)))) ;; => (let ((x 1)) (let ((b 1)) (let ((a (+ 1 1))) a)))
不要定義削除
文字通り必要のない変数定義を削除する。
(let ([var val]) body)
のような場合、val に副作用がなく、body に var が登場しなければ (let ...) は丸ごと body と置き換えても良い。副作用のあるなしは正確には判断できない。しかし手続き呼び出しは副作用がある、それ以外な副作用がないと考えれば良さそうだ。もっと厳密にいえば pair? なら副作用ありとする。
(define (find-in-body x body) (if (pair? body) (find (lambda (b) (find-in-body x b)) body) (if (equal? x body) x #f))) (define (can-eliminate-let? vars body) (let ([var1 (car (car vars))] [val1 (cadr (car vars))]) (not (or (pair? val1) (find-in-body var1 body))))) (define (eliminate sexp) (cond [(pair? sexp) (case (car sexp) [(let) (let ([vars (second sexp)] [body (car (map eliminate (cddr sexp)))]) (if (can-eliminate-let? vars body) body sexp))] [else sexp])] [else sexp])) (eliminate '(let ((x 1)) (let ((b 1)) (let ((a (+ 1 1))) a)))) ;; => (let ((a (+ 1 1))) a)
出来た。
もやもや
- A正規化の結果に現れる let は let1 の方が良いのでは?
- コードの肥大化と扱いやすさのどちらをとるべきか分からない
A正規化・K正規化・CPS
id:sumiiさんにご教授いただきました。
let の右辺による違いだというのは理解できたのですが、その後の最適化や実コード生成時にその違いがどう表れるかがまだ理解できていません。
何か論文かコードを読まないと。
A正規形とかCPSとか - sumiiの日記
次の1手
- let1 の A正規化のコードを書く
- let の A正規化のコードを書く
- 最適化を組み込んでみる