2. 2009/11/13 言語処理系入門 4 2
関数
我々の言語に関数を追加したい
# let double x = x + x in
double (3+4);
==> 14
# let abs n =
if n >= 0 then n else –n
in
abs (-123)
==> 123
# let square_add a b = a * a + b * b in
square_add 5 2;
==> 29
3. 2009/11/13 言語処理系入門 4 3
第一級市民 (first class object) とし
ての関数も扱いたい
高階関数
…関数を返す関数,関数を引数にとる関数,
例:
let twice f x = f (f x);
let make_adder x =
let adder y = x + y
in adder;
匿名関数
関数をいちいち定義せずにその場で作れる
twice (fn x -> x + x) 5;
(fn f x -> f x) (fn x -> x * x) 3;
4. 2009/11/13 言語処理系入門 4 4
クロージャ
関数を表すオブジェクト
<仮引数名,関数本体のコード,環境>の3つ組
み
環境は関数が生成される時点のもの
関数適用時,仮引数を実引数の値で対応させ
たエントリが環境に追加され,その新しい環
境のもとで,関数本体の式が評価される
例 :
let y = -9 in
fn x = x + y
x
x + y
変数 値
y -9
6. 2009/11/13 言語処理系入門 4 6
形式的意味
以下のように ( 抽象 ) 構文を拡張
E ::= E E 関数適用
V ::= fn x -> E 関数生成
値
v Val = Clos(x,E,ρ)∈
),,(| ρρ ExClosEx ⇓>− -fn
vEE
vEvxvEExClosE
⇓−
⇓−±⇓−⇓−
21
21
|
'|}''{'|)',','(|
ρ
ρρρρ
7. 2009/11/13 言語処理系入門 4 7
関数の実装
抽象構文の拡張( syntax.ml )
let rec expr = …
| AppExpr of expr * expr
and value = …
| FunVal of Symbol.t * expr
評価器の拡張 (eval.ml)
let rec eval_expr env e = match e with …
| AppExpr(e1,e2) -> clos_apply env
(Value.get_clos (eval_expr env e1)) e2
and eval_value env v = match v with …
| FunVal(x,e) -> Value.Clos(env,x,e)
and clos_apply env (env',x,e') e =
let xvs = eval_binds env [x,e] in
eval_expr (Env.extend env' xvs) e'
9. 2009/11/13 言語処理系入門 4 9
再帰的定義のための let rec 形式
再帰関数を定義するには,普通に let を使っ
てもダメ
let fact =
fn n -> if n == 0
then 1 else n * fact(n – 1)
in fact 5
let rec 形式を導入する
= の右辺式を拡張した環境の下で評価する
},,{'
|
|'1for|'
11
11
nn
nn
ii
vxvx
vEExEx
vEnivE
±=
⇓==−
⇓−≤≤⇓−
ρρ
ρ
ρρ
inandreclet
10. 2009/11/13 言語処理系入門 4 10
let rec の実装
eval.ml
let rec eval_expr env e = match e with …
| LetRecExpr(ds,e1) ->
let env' = eval_rec_decls env ds
in eval_expr env' e1
…
and eval_rec_decls env ds =
let env' =
Env.extend env
(List.map (fun (x,e) ->
x,Value.UndefinedObj) ds)
in
let xvs = eval_binds env' ds in
Env.updates env' xvs; env'
仮の値で環境
を拡張
後で正式な値
で環境を更新
11. 2009/11/13 言語処理系入門 4 11
let rec を使わない再帰関数定義
以下のように fix オペレータ(関数)を定義する
let fix f =
(fn x -> f (fn y-> x x y))
(fn x -> f (fn y-> x x y))
例: factorial 関数の定義
# let factorial =
fix (fn fct n ->
if n == 0 then 1
else n * fct (n – 1));
# factorial 5;
==> 60
演習問題:
fix オペレータが動くことを確認し,なぜ再帰を生み出すのか
考えよ
12. Syntax sugar(2)
begin ~ end 文の実装
[[begin E1; E2; … ; En end]]⇒
let _ = [[E1]] in let _ = [[E2]]in … in [[En]]
while 文の実装
[[while E1 do E2]]⇒
let rec loop x =
if x then begin [[E2]]; loop [[E1]]end
in
loop [[E1]]
2009/11/13 言語処理系入門 4 12
loop, x はフレッシュな
変数
( E1,E2 中に出現しな
い)
13. 参照呼び出し( Call-by-
Reference )
swap 関数を定義したい
# let x = 3 and y = 4;
# swap x y;
x ==> 4, y ==> 3 となる
以下のように定義してもうまくいかない
let swap x y =
let temp = x in
begin x := y; y := temp end;
引数 x と y は関数呼び出し時にコピーされてしまう
2009/11/13 言語処理系入門 4 13
)',(|,
)',(|},,{},,,{
1for),(|,
22110
111
1
SvEExExExS
SvElxlxvlvlS
niSvES
nn
nnnnin
iiii
⇓===−
⇓−±
≤≤⇓−−
inandandlet
ρ
ρ
ρ niSDOMl ni ≤≤∉ 1eachfor)(if
14. 参照呼び出しのセマンティクス
右辺式が変数ならば,コピーしない
環境の実装
環境に格納する値が,直接的な値
( DirectObject )か参照値( IndirectObject )か
を区別できるようにする
2009/11/13 言語処理系入門 4 14
)',(|,
)',(|}{,
SvEyxS
SvElxS
⇓=−
⇓−±
inletρ
ρ
)( ly =ρ
Direct Int 5
Indirect
x
y
15. 参照呼び出しの実装
参照はがし
let deref = function
| Value.DirectObj v -> v
| Value.IndirectObj loc -> (
match Loc.get_val loc with
| Value.DirectObj v -> v
| Value.IndirectObj _ ->
raise Invalid_reference_error
| Value.UndefinedObj x ->
raise (Undefined_variable_error x)
)
| Value.UndefinedObj x ->
raise (Undefined_variable_error x)
評価器
let rec eval_expr env e = match e with …
| VarExpr x -> deref (Env.lookup env x)
2009/11/13 言語処理系入門 4 15
16. 参照呼び出しの実装(2)
参照先設定
let setref loc v =
let loc' =
match Loc.get_val loc with
| Value.IndirectObj loc'' -> loc''
| _ -> loc
in
Loc.set_val loc' (Value.DirectObj v)
評価器 (eval_expr)
| AsgnExpr(x,e1) ->
let v = eval_expr env e1 in
setref (get_var_loc env x) v; v
2009/11/13 言語処理系入門 4 16
17. 参照呼び出しの実装(3)
変数束縛の処理
and eval_bind env (x,e) =
x,(match e with
| VarExpr x' ->
let loc = get_var_loc env x' in (
match Loc.get_val loc with
| Value.IndirectObj loc' ->
Value.IndirectObj loc'
| _ -> Value.IndirectObj loc
)
| _ -> Value.DirectObj(eval_expr env e)
)
2009/11/13 言語処理系入門 4 17
18. 名前呼び出し( Call by Name )
関数呼び出し時に,引数を評価しない.
関数の中で実際に引数を使うときに評価する
値呼び出しと名前呼び出しとでの重要な動作の
違い
# let rec loop x = loop x;
# let do_nothing x = 0;
# do_nothing (loop 0);
???
値呼び出しだと無限ループするが,名前呼び出
しだと停止する
実用的な使い途として無限ストリームの実装に使える
2009/11/13 言語処理系入門 4 18
19. 無限ストリームの例
// cons の手続き的な実装
let cons ?x ?y = fn m -> if m then x else y;
let car b = b true;
let cdr b = b false;
// 無限ストリームの実装
let generator n = cons n (generator (n + 1));
# let stream = generator 0;
# car stream;
==> 0
# car (cdr stream);
==> 1
# car (cdr (cdr stream));
==> 2
… 以下メモリの続く限り
2009/11/13 言語処理系入門 4 19
21. 演習課題
今週のサンプルプログラムを動かしてみよ
サンプルプログラムでは,値呼び出し,名前呼び出し参照呼び
出しを実装してある.
値呼び出しの場合
let x = E, let f x = E
参照呼び出しの場合
let &x = E, let f &x = E のように変数名の前に & をつける
名前呼び出しの場合
let ?x = E, let f ?x = E のように変数名の前に ? をつける
以下の挙動を確認せよ
let rec x = x;
let rec &x = x;
let rec ?x = x;
上の結果の違いをそれぞれ説明せよ
2009/11/13 言語処理系入門 4 21