2. これまでの評価器の構造
例:
| PrimAppExpr(op,es) ->
let vs = List.map (eval_expr env) es in
PrimOp.apply op vs
eval_expr が次にどの部分式を評価するかは,構文木の構造によって暗黙的
に決まっていた.
eval_expr env [[a + (f 5)]] の実行後,次に評価器が何をするのかは文脈に依
存
PrimAppExpr
+ AppExprVarExpr
a VarExpr
f
ValExpr
5
a + (f 5)
2009/12/4 2言語処理系入門 6
3. 命令型言語に特有の制御文
return, break, continue など
例:関数の本体中に以下の部分式があるとする
fn x a -> … a / (if x == 0 then return 0 else x) …
式の構造を飛び越えて計算を行うには,工夫が必要!
PrimAppExpr
/ IfExprVarExpr
a PrimAppExpr
==
RetnExpr VarExpr
xVarExpr ValExpr ValExpr
この部分式
を評価した
後,どうす
る?
2009/12/4 3言語処理系入門 6
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言語処理系入門 6
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言語処理系入門 6
6. 末尾呼び出し( tail call )
処理の最後にくるような関数呼び出し
関数呼び出しから戻ってきた後は,単に return するこ
とだけ
例: iter は末尾呼び出しになっている
let rec iter x =
if x == 0 then
()
else
begin print x; iter (x – 1) end;
CPS では,関数呼び出しは常に tail call となる
tail call は goto に変換できる(スタックを消費しない
)
2009/12/4 6言語処理系入門 6
7. 継続渡し評価器
次に何をするのか ( 継続 ) を評価器の引数として明
示的に与える
継続渡しスタイルの eval_expr
(*
* Env.t -> Cont.t -> Syntax.expr -> Value.t
*)
let rec eval_expr env cont e =
match e with …
継続 cont は,部分式 e を評価した結果を受け取り,最終的
な答えを得るまでの残りの計算.
トップレベルから eval_expr を呼び出すときには, (もう
何もすることが無いことを意味する)空の継続を渡してや
る
部分式 e を評価し終
えた後に,やるべき
計算
2009/12/4 7言語処理系入門 6
8. 継続の表現
継続の表現( cont.ml )
Value.t を受け取って Value.t を返す Ocaml の関数として表
現
type t = Value.t -> Value.t
実際には’ a -> ’ b の多相型関数として定義
空の継続
もらった値をそのまま返す
let init v -> v
はじめの一歩 (main.ml)
Syntax.Expr e ->
print_result
(Eval.eval_expr env Cont.init e)
2009/12/4 8言語処理系入門 6
12. 例外処理
例外の捕捉
E ::= try E1 handle x in E2
例外発生
E ::= raise E1
プログラム例:
let f x y =
if y == 0 then raise @Divided_zero
else x / y
in
try f 10 0 handle x in
case x of @Divided_zero -> 0
| * -> raise x
2009/12/4 12言語処理系入門 6
13. 例外ハンドラの管理
例外ハンドラ管理モジュール( exn.ml )
例外ハンドラは,単なる継続として表現
type t = (Value.t, Value.t) Cont.t
デフォルトのハンドラ定義
Ocaml の例外を投げてトップレベルに戻る
let handler:t ref =
ref (fun v ->
raise (Unhandled_exception_error v))
ハンドラの設定
let set_handler h = handler := h
ハンドラの取得
let get_handler() = !handler
2009/12/4 13言語処理系入門 6
14. 例外処理の実装
let rec eval_expr env cont e = match e with
…
| TryExpr(e1,s,e2) ->
let old_h = Exn.get_handler() in
let new_h exn_v =
Exn.set_handler old_h;
eval_expr
(Env.extend env [s,Value.DirectObj exn_v])
cont e2
in
Exn.set_handler new_h;
eval_expr env
(fun v -> Exn.set_handler old_h; cont v) e1
| RaiseExpr e1 ->
eval_expr env (fun v -> (Exn.get_handler()) v)
e1
元のハンドラ
をセットし直
す
新しいハンド
ラをセットす
る
新しい
ハンドラ
定義
2009/12/4 14言語処理系入門 6
15. 一級継続( first class
continuation )
プログラマに,継続を直接操作する手段を提供する
letcc 構文
E ::= letcc k in E1| throw E1 with E2
現在の継続( letcc を取り囲んでいる文脈)を k に束縛し
, E1 を実行する
例:
# 3 + (letcc k in 2 + (throw k with 5) * 1);
==> 8
変数 k には fn x->3+x という継続を表す関数が束縛され
る
継続に値を渡すと(現在の継続を捨てて)その継続を実行
する
呼び出し元には戻らない
ジャンプする
2009/12/4 15言語処理系入門 6
16. 一級継続の応用
深い関数呼び出しからの脱出
let prod ls =
letcc k in
let rec iter result ls_ =
case ls_ of
@Nil -> result
| @Cons x ->
if x.car == 0 then throw k with 0
else iter (result * x.car) x.cdr
in iter 1 ls
2009/12/4 16言語処理系入門 6
17. letcc による break, continue の実現
Syntax Sugar として実装できる
while 式
[[while E1 do E2]]⇒
letcc break in
let rec loop x =
if x then begin
letcc continue in [[E2]]; loop [[E1]]
end
in loop [[E1]]
break/continue
[[break]] ⇒ throw break with ()
[[continue]] ⇒ throw continue with ()
2009/12/4 17言語処理系入門 6
19. 例外を考慮に入れた letcc の実装
try ブロック中で継続により脱出した場合,例外ハンドラが残ってしま
う
# letcc k in try k 0 handle c in c;
==> 0
# raise 10;
==> 10???
逆に,継続によって try ブロック内に突入した場合,例外ハンドラがセット
されない
正しい実装
| LetccExpr(x,e1) ->
let h = Exn.get_handler() in
eval_expr (
Env.extend env
[s,Value.DirectObj(Value.Cont
(fun v -> Exn.set_handler h;
cont v))]
) cont e1
継続が呼ばれたら
,元のハンドラを
セットし直す
2009/12/4 19言語処理系入門 6
20. 限定継続( delimited
continuation )
継続に区切りを付け,複数の継続を関数のよ
うに合成できるようにしたもの.
shift k in E
現在の継続を捕捉して E を評価.その後,現在
の継続を捨てて , 一番最近の reset まで戻る
reset E
継続の仕切り直し
init))}((.{]][[]][[ κκλρκρ vkkvxEEx ΕΕ = inshift
)]][[(]][[ initκρκκρ EE ΕΕ = reset
2009/12/4 20言語処理系入門 6
21. shift/reset の実行例
# 1 + (reset 3);
==> 4
# 1 + (reset (2 * (shift k in 4)));
==> 5
# 1 + (reset (2 * (shift k in k 4)));
==> 9
# 1 + (reset (2 * (shift k in k (k 4))));
==> 17
2009/12/4 21言語処理系入門 6
22. shift/reset による並行プロセスの実装
let ready_queue = Queue.new();
let rec dispatch _ =
if !ready_queue.is_empty() then ready_queue.get() ()
and yield _ =
shift k in begin
ready_queue.put
(fn -> begin k (); dispatch() end);
dispatch()
end
and spawn ?proc = begin
ready_queue.put
(fn -> begin reset proc; dispatch() end);
yield ()
end;
2009/12/4 22言語処理系入門 6