|
<式> | ::= | … |
| | (letrec (<再帰関数定義リスト>) <本体式>) | |
<再帰関数定義リスト> | ::= | (<識別子1> (lambda (<識別子11> … <識別子1n1>) <式1>)) |
… (<識別子m> (lambda (<識別子m1> … <識別子mnm>) <式m>)) |
(letrec ((fact (lambda (n) (if (= n 0) 1 (* n (fact (sub1 n))))))) (fact v))この構文の基本的なセマンティクスは let 式と似ていて,環境を宣言 にしたがって拡張したもとで本体式を評価するものである.ただし,環境を拡 張する際に,再帰的な定義を処理する工夫が必要になる.各変数の有効範囲は, 宣言される関数式を含むため,それらの関数の閉包を作るときの環境は,本体を 評価するときの環境と同じもの,つまり,これから得られるはずの拡 張された環境でなくてはならない.これを扱うために,ここでの実装は,Objective Caml の配列の更新機能を利用する.
図13が,parser.mly,lexer.mll を除く,プログラムの変 更点である.eval_exp の Letrec を処理する部分は,Let の処理とほ とんど同じである.環境に関する新しい操作 extend_env_rec が重要な部分 である.extend_env_rec は,同時に宣言される変数名・関数(パラメータリ ストと本体式の組)・環境を受け取る.最初に宣言される vec は, 後で関数閉包が格納される配列で,初期値として,ダミーの整数値を いれている.newenv は,vec と宣言される変数名で拡張した 新しい環境である.次はsyntax.ml:
type exp = ... | Letrec of (id * (id list * exp)) list * exp
core.ml:
let extend_env_rec syms procs env = (* assumes List.length syms = List.length procs *) let vec = Array.make (List.length syms) (IntV 0) in let newenv = ExtendEnv (syms, vec, env) in let rec iter f i = function [] -> () | x :: ls -> f i x; iter f (i + 1) ls in let iteri f = iter f 0 in iteri (fun i (ids, body) -> vec.(i) <- ProcV (ids, body, newenv)) procs; newenv let rec eval_exp env = function ... | Letrec (procdefs, body) -> let (ids, procs) = List.split procdefs in let newenv = extend_env_rec ids procs env in eval_exp newenv body
Figure 13: 再帰的関数定義
(int -> 'a -> unit) -> 'a list -> unit型を持つ補助関数 iteri の宣言で,
iteri f [a0; a1; ... an]は
f 0 a0; f 1 a1; ...; f n anを実行する.これを使って, newenv を,関数閉包を束縛するように更新している. 関数閉包は,拡張された環境 newenv を使って作られていることに注意. 最終的には,更新された環境を返している.