4. 復習:継続( continuation )とは
残りの計算を表す概念
プログラムの実行のある時点から最終的な答えを
得るまでの計算
例:
let x = 2 in
let y = 3 + x * (f x) in
y * y;
部分式 (f x) に注目したとき,このときの継続
let y = 3 + x * [<(f x) の値 >] in y * y
2009/12/4 4言語処理系入門 62009/12/18 4言語処理系入門 8
5. 復習:継続渡しスタイル
( CPS )
継続を明示的に渡す関数のスタイル
ダイレクトスタイル(通常の形式)
let rec fact n =
if n == 0 then 1
else n * (fact (n – 1));
継続渡しスタイル
let rec fact n k =
if n == 0 then k 1
else fact (n-1) (fn v -> k (v * n));
fact(n-1) を計算し
た後にする計算
(継続)
2009/12/4 5言語処理系入門 62009/12/18 5言語処理系入門 8
6. 中間言語としての CPS
制御の流れ , データの流れがコードに明示的に現れる
最適化がしやすい(末尾呼び出し最適化,不要コードの除去
etc )
shift/reset, callcc, letcc などの一級継続も CPS に変換することで
簡単に実装できる
例:
let rec prod_primes n =
if n == 1 then
1
else
if is_prime n then
n * prod_primes(n-1)
else
prod_primes(n-1)
2009/12/18 6言語処理系入門 8
7. 中間言語としての CPS ( 2 )
let rec prod_primes c n =
if n == 1 then c 1
else let k = fn b ->
if b == true then
let j = fn p ->
let a = n * p in c a
and m = n – 1
in prod_primes j m
else
let h = fn q -> c q
and i = n – 1
in prod_primes h i
in is_prime k n
k: 継続 1
j: 継続
2
h: 継続
3
2009/12/18 7言語処理系入門 8
8. ソース言語の抽象構文
E ::= F | V
| let x1 = E1 and … and xn = En in E
| let rec x1 = E1 and … and xn = En in E
| if E1 then E2 else E3
| handle E1 with x in E2
| raise E
| E.l
| E1 E2
| op(E1,…,En )
| { l1=E1; …; ln=En }
| fn x -> E
V ::= c | x
op は +,-,*,/,strlen など
のプリミティブ演算子
2009/12/18 8言語処理系入門 8
9. CPS 式の構文
K ::= E
| let x1 = D1 and … and xn = Dn in K
| let rec x1 = D1 and … and xn = Dn in K
| if E then K1 else K2
| V k E | k E
D ::= E
|{ l1=V1;…; ln = Vn }
| fn k x -> K | fn x -> K
E ::= V
| let x1 = D1 and … and xn = Dn in E
| let rec x1 = D1 and … and xn = Dn in E
| E.l
| op(E1, … ,En)
V ::= c | x | k --- ただし, k, x∈Variable
関数呼び出しや分岐を
伴わない単純な式
レコードと関数は let/let rec で束縛
されてから使用される
2009/12/18 9言語処理系入門 8
11. if 式
素朴な変換
[[if E1 then E2 else E3]]к⇒
[[E1]](λv.if v then [[E2]]к else [[E3]]к)
к が複製されるため,コードが爆発する可能性あ
り
正しい変換
[[if E1 then E2 else E3]]к⇒
[[E1]](λv.let j = fn x ->кx
in if v then [[E2]](λv.j v)
else [[E3]](λv.j v))2009/12/18 11言語処理系入門 8
12. let/let rec 式
x=E の右辺式 E を単純な式に変換
let 式
[[let x1 = E1 … and xn = En in E]]к⇒
[[Es]](λvs. к (let xs=vs in [[E]]кinit))
[[Es]](λvs.let xs=vs in [[E]]к)
let rec 式
[[let rec x1 = E1 … and xn = En in E]]к⇒
[[Es]](λvs.к(let rec xs=vs in [[E]]кinit))
[[Es]](λvs.let rec xs=vs in [[E]]к)
2009/12/18 12言語処理系入門 8
E is simple
E is not simple
E is simple
E is not simple
13. 関数・プリミティブ演算子の変換
無名関数
[[fn x -> E]]к⇒
let f = fn k x -> [[E]](λv.k v) in кf
f や k はフレッシュな変数
後のフェーズで便利なように無名の関数に名前を付けてお
く
関数適用
[[E1 E2]]к⇒
[[E1]](λf.[[E2]](λv.let k = fn r ->кr in f k v))
プリミティブ演算子
[[op(E1,…,En)]]к⇒
[[Es]](λvs.кop(vs))
呼び出し元に戻ってくるための継続
継続を受け取るた
めの引数を1つ増
やす
2009/12/18 13言語処理系入門 8
14. レコード式の変換
レコード生成
各フィールドの右辺式を単純な式に変換
[[{ l1=E1; …; ln=En }]]к⇒
[[Es]](λvs.к(let xs=vsin let r ={ls=xs} in r))
フィールド参照
参照元の式を変換し,そのフィールド値を x に束
縛
[[E.l]]к⇒
[[E]](λv.к(v.l))
2009/12/18 14言語処理系入門 8
15. 例外捕捉・発生
例外捕捉
新しいハンドラをセットし,式を評価
例外を捕捉したら元のハンドラをセットし直して, E2 を
評価
[[handle E1 with x in E2]]к⇒
let h = gethdlr()
and k = fn x ->кx in
let h’ = fn x -> let _ = sethdlr h in
[[E2]](λv.k v)
in let _ = sethdlr h’ in
[[E1]](λv.let _ = sethdlr h in k v)
例外発生
ハンドラを取得して,呼び出すだけ
[[raise E]]к ⇒
[[E]] (λv.let h = gethdlr() in h v)
2009/12/18 15言語処理系入門 8
16. 変換例
[[+(x,(f 0).l)]]к
⇒[[<x;(f 0).l>]](λvs.let x1 = +(vs) in кx1)
⇒[[ (f 0).l]](λv.let x1 = +(x,v) in кx1)
⇒[[(f 0)]](λv.let x2=v.l in (λv.let x1 = +(x,v) in кx1)x2)
⇒[[(f 0)]](λv.let x2=v.l in let x1 = +(x, x2) in кx1)
⇒[[f]](λf.[[0]] (λv.let k=fn r->
(λv.let x2=v.l in let x1 = +(x,x2) in к x1)r
in f k v))
⇒[[f]](λf.[[0]](λv.let k=fn r->
let x2=r.l in let x1 = +(x,x2) in кx1
in f k v))
⇒let k=fn r->let x2=r.l in
let x1 = +(x,x2)
in кx1
in f k 0
演習問題
[[let rec fact = fn n -> if ==(n,0) then 1 else *(n,fact(-(n,1))) in fact 5]]к
⇒?
2009/12/18 16言語処理系入門 8
17. Ocaml での実装( cps.ml )
let rec conv_expr k e = match e with
| Syntax.ValExpr v -> k (conv_value v)
| Syntax.IfExpr(e1,e2,e3) ->
let j = Symbol.fresh() in
let x = Symbol.fresh() in
let k' = fun v -> AppExpr(VarVal j,[v]) in
conv_expr (
fun v ->
LetExpr([j,FunExpr([x],k (VarVal x))],
IfExpr(v,conv_expr k' e2,conv_expr k' e3))
) e1
| Syntax.FunExpr((x,_),_,e1) ->
let k’ = Symbol.fresh() in
let f = FunExpr([k’;x],
conv_expr (fun v -> AppExpr(VarVal k’,[v])) e1)
in
k (VarVal f)
…
2009/12/18 17言語処理系入門 8
18. 末尾呼び出しの最適化
以下の式を CPS 変換すると
let f x = g (x + 1)
こうなるが こう最適化でき
る
let f k x = let f k x =
let k’ a = k a in let b = x + 1 in
let b = x + 1 in g k b
g k’ b
λ 計算において λx.M(x) を M に簡約化することを η
簡約と呼ぶ
「 C 言語的に考えると, int foo(int x) { return bar(x); } とい
う関数定義がある場合,すべての foo の呼び出しを bar に
置き換えることができる」のと同じことである
2009/12/18 18言語処理系入門 8