type exp = ... | Fun of string * exp (* fun x -> e *) | App of exp * exp (* function application i.e. e e *)これに対応して「式の型」をあらわす型も拡張する必要がある。 関数の型は、(int -> bool) のように矢印を使って表現する。 これを、TArrow(TInt, TBool) と表現することにしよう。 ただ、関数の定義域や値域がさらに関数型であることもある。 たとえば、 (int -> (int -> bool)) や ((int -> bool) -> int) といった ものも型である。(このような「高階関数」が使えることが、関数型言語の 特徴の1つである。) そこで、拡張された ty型は以下のように定義される。
type ty = TInt | TBool | TArrow of ty * tyこの定義を使えば、 (int -> (int -> bool)) に対応する内部表現は, TArrow(TInt, TArrow(TInt, TBool)) となる.
(* tcheck3 : tyenv -> exp -> ty *) let rec tcheck3 te e = match e with ... | Fun(x, e1) -> let t1 = lookup x te in let t2 = tcheck3 te e1 in TArrow(t1,t2) | App(e1,e2) -> let t1 = tcheck3 te e1 in let t2 = tcheck3 te e2 in begin match t1 with | TArrow(t10,t11) -> if t2=t10 then t11 else failwith "type error in App" | _ -> failwith "type error in App" end | _ -> failwith "unknown expression"
[Hint] この実験の範囲では、(let x = e1 in e2)という式は、 ((fun x -> e2) e1) という式と同じである。 そこで、既に実装した Fun と App の型付けを利用すればよい。
例題としては、以下のものを使うとよい。
補足: tcheck3 では,関数の引数となる変数の型も, 型環境で与えなければいけない.そのため,異なる関数の引数がx という同じ 名前であったとき,それらの型も同じでなければいけない,という(OCamlにはない) 制約がある. たとえば (fun x-> x) (fun x -> x + 1) という式は、OCaml では型がつく が、tcheck3 では型がつかない。なぜなら、2つの束縛変数x は OCaml では 異なるものであるのに、tcheck3 は区別せずに同じ型を持つ変数と見なしている からである。
この問題を克服するには、 型環境をつかうのではなく,関数の仮引数(変数)ごとに型を記述するスタイルにすれば よい.この場合,「式」の構文の中に,「型」を含む表記を許すよう 拡張しなければいけないので,本実験では,この方向は深追いしないが、 もし興味がある人がいたら、拡張してみるとよい。 たとえば、以下のようにするとよい。
type exp = ... | Fun of string * ty * exp (* fun (x:t) -> e *) | ...この場合、Fun の構文を変更するので、今まで実装した Fun を扱う部分も全て 変更する必要がある。 なお、後に述べる型推論の考え方を使えば、この問題点も自然に解消する.
亀山 幸義