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 を扱う部分も全て変更する必要がある。なお、後に述べる型推論の考え方を使えば、この問題点も自然に解消する。
亀山 幸義