元は" The 90 minute Scheme to C compiler" Marc Feeley (90-min-scc.pdf) 。元の文章との整合性は保証しないよ。CPS が何かわかって、コンパイラを作ることに興味が出たら幸い。
ただし、これだけではコンパイラはできないよ。Scheme で作りたいなら、http://www.eidos.ic.i.u-tokyo.ac.jp/~tau/lecture/scheme_compiler/gen/resume/all.pdf がおすすめ。
将来的に浮動小数点を含むコンパイラを作りたいなら、あらかじめ OCaml をつかったコンパイラを目指した方がよい(らしい)。
https://esumii.github.io/min-caml/jpaper.pdf
https://github.com/esumii/min-caml
A正規形とK正規形というのもある(らしい)。
http://d.hatena.ne.jp/sumii/20071229/p1
論文のThe Essence of Compiling with Continuations には A 正規形の話があり、CPS が否定されているように思った。
11. ファースト?クラスの継続(4)
? 例 2: backtracking
? 次の式の X,Y,Z を求めるとしよう
2<= X, Y, Z <=9 and X2 = Y2 + Z2
? in-range と fail はどう実現しているか?興味深いでしょ?
(let ((x (in-range 2 9))
(y (in-range 2 9))
(z (in-range 2 9)))
(if (= (* x x)
(+ (* y y) (* z z)))
(list x y z)
(fail))) ; => (5 3 4)
12. ファースト?クラスの継続(5)
(define fail
(lambda () (error "no solution")))
(define in-range
(lambda (a b)
(call/cc
(lambda (cont)
(enumerate a b cont)))))
(define enumerate
(lambda (a b cont)
(if (> a b)
(fail)
(let ((save fail))
(set! fail
(lambda ()
(set! fail save)
(enumerate (+ a 1) b cont)))
(cont a)))))
14. Scheme のサブセット
? 実際のフルスペックの Scheme をコンパイルするのは
難しいんだよね。そこで簡単な Scheme のサブセットを
扱うことにするよ。
– プリミティブは(+, -, +, =, <, display) にとどめ、すべて整数
のみの対応。そして call/cc も考慮しよう
– Only small exact integers and functions (and #f=0/#t=1)
– Only the main special forms and no macros
– set! で許される代入はグローバル変数へのみ
– No variable-arity functions
– No error checking
? 残りの実装は読者への宿題としよう。
15. クロージャ変換(1)
? 問題:自由変数(*1)へのアクセスはどうする?
? どのようにして関数 F 内で x, y の値を得たら
よいと思う?
(lambda (x y z)
(let ((f (lambda (a b)
(+ (* a x) (* b y)))))
(- (f 1 2) (f 3 4))))
(*1) ヤクチュウ。いきなり自由変数といってもなんのことやら。
コンパイラでは必ず出てくる話です。事前に勉強しておきましょう。
16. クロージャ変換(2)
? 最初に思いつく方法として自由変数を引数に入れ込んでしまうと
いうのがあるね。
? この変換はラムダ?リフティング(lamda lifting)と呼ばれていて、この
ケースではうまくいくけど、万能ではないんだ(*1)。
? そこでクロージャ(closure)の登場だ。自由変数を関数と共に一つ
のオブジェクトにパッケージする方法だ。
(lambda (x y z)
(let ((f (lambda (x y a b)
(+ (* a x) (* b y)))))
(- (f x y 1 2) (f x y 3 4))))
(*1) ヤクチュウ。下のように関数を返すケースでは x y を入れ込みようがない。
(lambda (x y z)
(let ((f (lambda (a b)
(+ (* a x) (* b y)))))
f))
17. クロージャ変換(3)
? 次に考えられる方法は関数呼び出し時に自由変数を含む
構造体のようなものを作り出し(*1)関数の引数にも追加する
ことだ。
? この方法で自由変数の排除ができるね。
? 各ラムダ式は(関数そのものじゃなくて)一種の指示書のよ
うなブロックを表すことになるんだ。(just like in C)
(lambda (x y z)
(let ((f (vector
(lambda (self a b)
(+ (* a (vector-ref self 1))
(* b (vector-ref self 2)))
x
y)))
(- ((vector-ref f 0) 1 2) ((vector-ref f 0) 3 4))))
(*1) ヤクチュウ。一応書いておくよ。原文は build a structure で
構造と書いてあるけど、C を思わせる構造体とは書いていない。
18. クロージャ変換のルール
? (lambda (P1 … Pn) E) =
(vector (lambda (self P1…Pn) E) v…)
v… で表されているのが自由変数のリスト
(lambda (P1…Pn) E)
? v = (vector-ref self i)
v は自由変数で i はラムダ式をも含むオブジェクトにある自
由変数のリスト内の位置
? (f E1…En) = ((vector-ref f 0) f E1 … En)
この変換は f が変数であっても、CPS変換の後であろうとも
なりたつということに注目してほしい。f=(lambda…) のよう
な特別な場合の扱いは例外だけどね。
? そして、クロージャとクロージャの参照(closure-ref)は動的
型付けを可能とする点も重要だ
25. CPS 変換のルール(1)
? まずは表現方法(notation)を定義しよう
? これは E という Scheme 式をCPS 変換した結果の
Scheme の式。C の Scheme 式は E の継続を表す。
? E は元の式で(末尾からではない呼び出しを含むか
もしれない) C は CPS 形式の式(末尾呼び出ししか含
まない)になる。
? C は変数かあるいは lambda 式になる。
C
E