まず、if を追加する。これは、原理的には簡単であるが、 処理系の上では 少し準備がいる。なぜなら、if をいれるためには、true や falseも いれなければいけないが、そうすると、式を評価した結果が、整数とは限らず、 真理値(Boolean)かもしれないからである。OCaml は型つきの言語なので、 「整数も真理値も表す変数」というものは単純には表せない。
これで困ったように思えるが、OCaml では、 「整数型と真理値型の両方を合わせた型」を定義することが できる。(集合の言葉でいえば、「直和」である。) そこで、このような型を定義して使うことにする。 なお、評価した結果、返ってくる式のことを、 値(あたい, value) と呼ぶので、ここでは value という名前の 新しい型を定義する。
(* 式の型 *)
type exp =
IntLit of int
| Plus of exp * exp
| Times of exp * exp
| BoolLit of bool (* 追加分; 真理値リテラル, つまり trueや false *)
| If of exp * exp * exp (* 追加分; if-then-else式 *)
| Eq of exp * exp (* 追加分; e1 = e2 *)
| ...
(* 値の型 *)
type value =
IntVal of int (* 整数の値 *)
| BoolVal of bool (* 真理値の値 *)
つまり、前回のインタープリタでは、eval e の結果は、
1 や 3といった整数値となるように設計していたが、
今回作成するインタープリタでは、
(IntVal 1) や (BoolVal true) といった value型の値を
返すようにするのである。ここで IntVal や BoolVal というのは
どういう型の値かを示すタグの役割を果たしている。
ポイントは、(繰返しになるが)、value というのは1つの型であるため、
「eval e の返す型は value である」と言えるようになることである。
また、今回から、(1+true) のように、式としてはあり得るが、計算としては 誤っているものが与えられる可能性がでてくる。 その場合、今回の実験で作成するインタープリタでは、 例外を発生させて、処理をその段階で終えるようにする (実際の処理は後述の通り、failwith という関数を使う)。
さて、このように、インタープリタが返す値が、 value型になると eval1 も変更する必要がある。 変更後の新しいインタープリタは eval2という名前とする。
(* eval2 : exp -> value *)
let rec eval2 e =
match e with
IntLit(n) -> IntVal(n)
| Plus(e1,e2) ->
(match (eval2 e1, eval2 e2) with
(IntVal(n1),IntVal(n2)) -> IntVal(n1+n2)
| _ -> failwith "integer values expected")
| Times(e1,e2) ->
(match (eval2 e1, eval2 e2) with
(IntVal(n1),IntVal(n2)) -> IntVal(n1*n2)
| _ -> failwith "integer values expected")
| _ -> failwith "unknown expression")
なんだか、急に難しくなったように感じるかもしれないので細かく見よう。
プログラムの全体構造は eval1と同じであり、式eについての パターンマッチで場合分けしている。
eval1では、足し算の場合、(eval1 e1) + (eval1 e2) を返していた だけだが、今度は、もう一度 match文を使って、複雑な処理をしている。これ はなんだろうか?
eval2 (Plus(IntLit 1, IntLit 2)) eval2 (Plus(IntLit 1, BoolLit true)) eval2 (Plus(BoolLit true, IntLit 2)) eval2 (Plus(BoolLit true, BoolLit true))どのような結果になっただろうか。また、それは、なぜだろうか?
(* eval2 : exp -> value *)
let rec eval2 e =
match e with
IntLit(n) -> ... (* 上のプログラムと同じ *)
| Plus(e1,e2) -> ...
| Times(e1,e2) -> ...
| Eq(e1,e2) ->
(match (eval2 e1, eval2 e2) with
(IntVal(n1),IntVal(n2)) -> BoolVal(n1=n2)
| (BoolVal(b1),BoolVal(b2)) -> BoolVal(b1=b2)
| _ -> failwith "wrong value")
| BoolLit(b) -> BoolVal(b)
| If(e1,e2,e3) ->
(match (eval2 e1) with
BoolVal(true) -> eval2 e2
| BoolVal(false) -> eval2 e3
| _ -> failwith "wrong value")
| _ -> failwith "unknown expression e"
(* テスト *)
let _ = eval2 (IntLit 1)
let _ = eval2 (IntLit 11)
let _ = eval2 (Plus (IntLit 1, Plus (IntLit 2, IntLit 11)))
let _ = eval2 (Times (IntLit 1, Plus (IntLit 2, IntLit 11)))
let _ = eval2 (If (Eq(IntLit 2, IntLit 11),
Times(IntLit 1, IntLit 2),
Times(IntLit 1, Plus(IntLit 2,IntLit 3))))
let _ = eval2 (Eq (IntLit 1, IntLit 1))
let _ = eval2 (Eq (IntLit 1, IntLit 2))
let _ = eval2 (Eq (BoolLit true, BoolLit true))
let _ = eval2 (Eq (BoolLit true, BoolLit false))
亀山 幸義