Upload
kenta-hattori
View
103
Download
0
Embed Size (px)
DESCRIPTION
会社でやっていたソフトウェア基礎講座での講義資料
Citation preview
言語処理系入門
第4回:関数の実装2009 年 11 月 13 日(金)服部 健太
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
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;
2009/11/13 言語処理系入門 4 4
クロージャ
関数を表すオブジェクト <仮引数名,関数本体のコード,環境>の3つ組み 環境は関数が生成される時点のもの
関数適用時,仮引数を実引数の値で対応させたエントリが環境に追加され,その新しい環境のもとで,関数本体の式が評価される
例 :let y = -9 in
fn x = x + y
x
x + y
変数 値
y -9
2009/11/13 言語処理系入門 4 5
クロージャが不要な場合
C 言語の場合 関数の入れ子定義はできない. 関数ポインタはある⇒ 単なる関数の開始アドレス
Pascal 言語の場合 関数を返す関数は定義できない 関数の入れ子定義はできる⇒ アクセスリンクを辿って,外側の変数にアクセ
ス可能
2009/11/13 言語処理系入門 4 6
形式的意味
以下のように ( 抽象 ) 構文を拡張E ::= E E 関数適用V ::= fn x -> E 関数生成
値v Val = Clos(x,E,ρ)∈
),,(| ExClosEx -fn
vEE
vEvxvEExClosE
21
21
|
'|}''{'|)',','(|
2009/11/13 言語処理系入門 4 7
関数の実装
抽象構文の拡張( syntax.ml )let rec expr = … | AppExpr of expr * exprand 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)) e2and 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'
2009/11/13 言語処理系入門 4 8
Syntax sugar
複数の引数を持つ関数[[fn x1 x2 … xn -> E]]⇒
fn x1 -> (fn x2 -> … (fn xn -> [[E]])…)
let による関数定義[[let f x1 … xn = E]]⇒
let f = [[fn x1 … xn -> [[E]] ]]
関数適用[[E1 E2 … En]]⇒
(…(([[E1]] [[E2]]) [[E3]]) … [[En]])
カリー化という
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
11nn
nn
ii vxvxvEExEx
vEnivE
inandrec let
2009/11/13 言語処理系入門 4 10
let rec の実装
eval.mllet 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'
仮の値で環境を拡張
後で正式な値で環境を更新
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オペレータが動くことを確認し,なぜ再帰を生み出すのか考えよ
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 中に出現しない)
参照呼び出し( 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 1each for )( if
参照呼び出しのセマンティクス
右辺式が変数ならば,コピーしない
環境の実装 環境に格納する値が,直接的な値( DirectObjec
t )か参照値( IndirectObject )かを区別できるようにする
2009/11/13 言語処理系入門 4 14
)',(|,
)',(|}{,
SvEyxS
SvElxS
inlet
)( ly
Direct Int 5
Indirect
x
y
参照呼び出しの実装
参照はがし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
参照呼び出しの実装(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
参照呼び出しの実装(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
名前呼び出し( Call by Name ) 関数呼び出し時に,引数を評価しない.
関数の中で実際に引数を使うときに評価する 値呼び出しと名前呼び出しとでの重要な動作の
違い# let rec loop x = loop x;# let do_nothing x = 0;# do_nothing (loop 0);???
値呼び出しだと無限ループするが,名前呼び出しだと停止する 実用的な使い途として無限ストリームの実装に使え
る
2009/11/13 言語処理系入門 4 18
無限ストリームの例
// 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
名前呼び出しの実装
関数適用のとき,引数を評価せず,かわりにサンクオブジェクト(引数なしの関数)を作成し,束縛する.(* サンクオブジェクトを作成する *)and thunk env e = Value.ThunkObj (fun () -> eval_expr env e)
変数を評価するときに,対象がサンクオブジェクトの場合は,サンクオブジェクトを呼び出して結果を得る. サンプルプログラムでは deref 関数内で処理let deref = function… | Value.ThunkObj t -> t()
2009/11/13 言語処理系入門 4 20
演習課題
今週のサンプルプログラムを動かしてみよ サンプルプログラムでは,値呼び出し,名前呼び出し参照呼び
出しを実装してある. 値呼び出しの場合
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
2009/11/13 言語処理系入門 4 22
次回予定
日時: 2009 年 11 月 20 日(金) 17 : 00 - 18 : 30
場所: LB2 3F/A 会議室
内容: いろいろなデータ型の扱い
タグ付きレコード,文字列 etc.