(* 処理対象となる式の型の定義 *) type exp = | IntLit of int | Plus of exp * exp | Times of exp * exp | ... (* そのほか適当なものを追加 *) (* eval1 : exp -> int *) let rec eval1 e = match e with | IntLit(n) -> n | Plus(e1,e2) -> (eval1 e1) + (eval1 e2) | Times(e1,e2) -> (eval1 e1) * (eval1 e2)これだけである。あまりにも簡単であることに驚くだろう。
ここで、eval1 という関数が再帰関数であるため、 let の後に rec というキーワードを使っていることに注意せよ。 (rec をはずして、どのようなエラーになるか試してみよ。)
もう1つのポイントは、match ... with ... という構文で、 式 e に対するパターンマッチを行い、 その種類に応じて異なる処理をしている点である。 このパターンマッチは OCaml でプログラムを書くときに極めて重要な機能なので、 この段階で色々試してしっかり修得してほしい。
上記のインタープリタ (eval1) は、今後どんどん拡張していく予定である。 その際、「構文上はexp型の式であるが、まだ、その処理方法を記述していない (自分の作っているインタープリタがその処理方法を知らない)」 というケースが発生する。 そのような場合に、エラーが発生するようにしておこう。 このためには、「例外」(exception)という仕組みを使う。 ここでは、新しい例外を定義するかわりに、もともと用意されている failwith という関数を使い、eval1 を以下のように変更する。
let rec eval1 e = match e with | IntLit(n) -> n | Plus(e1,e2) -> (eval1 e1) + (eval1 e2) | Times(e1,e2) -> (eval1 e1) * (eval1 e2) | _ -> failwith "unknown expression"すると、exp型の構成要素であるのに、 IntLit(n), Plus(e1,e2), Times(e1,e2)のどのパターンにもあてはまらない要素を与えると、 "unknown expression"という表示を与えるエラーが発生する。 (なお、正確には「エラー」ではなく、「Failure という名前の例外」を発生するのであるが、ここでは その詳細を気にする必要はない。) なお、failwith の引数は、文字列1つである。
ここで "_" (アンダースコアの文字) は、 「どんな式とでもマッチするパターン」である。 変数 x も「どんな式とでもマッチするパターン」であるので、 上記のプログラムは、
... | Times(e1,e2) -> (eval1 e1) * (eval1 e2) | x -> failwith "unknown expression"と書いてあっても、まったく同じ動作をする。 つまり、この行の上に書いてあるパターンマッチが全部失敗したら、 必ず、この行でパターンマッチに成功して、failwith が実行される。 (C言語やその他の言語にもある「defaultのケース」を表す表現である。)
変数 x でもよいのに、なぜ "_" という読みにくい表現を使うのだろうか? 「この x は右辺では使わないので、 わざわざ "x" という名前をつける必要がない」というのが理由である。 「つけてもよいが、必要ない」場合に、 そのことが(プログラムを読んでいる人にとって)わかるようにすると、 プログラムの意味が理解しやすいため、わざわざ "_" を使うのである。
ここまで読むと、関数型プログラマとは変なことにこだわる人種と思うかもしれない。 確かにそうである。 しかし、プログラムを後から読む人(自分自身かもしれない)のことを考えると、 このように細部にまでこだわって美しく書くことが大事である。 プログラムを美しく書くこと--そう、それが実験の本当のテーマである。
テスト・データ:
let e10 = IntLit(1) ;; let e11 = Times(IntLit(2),IntLit(3)) ;; let e12 = Plus(IntLit(1),Times(IntLit(-2),IntLit(3))) ;; let e13 = Plus(Times(IntLit(4),Times(IntLit(1),IntLit(10))),IntLit(5)) ;; let e14 = Times(e2,e3) ;;
let rec eval1_bad e = match e with | IntLit(n) -> n | Plus(e1,e2) -> (eval1_bad e1) + (eval1_bad e2) | _ -> failwith "unknown expression" | Times(e1,e2) -> (eval1_bad e1) * (eval1_bad e2)
テスト・データ:
let e20 = Minus(IntLit(2),IntLit(3)) ;; let e21 = Div(IntLit(6),IntLit(3)) ;; let e22 = Times(IntLit(1),Minus(IntLit(-2),IntLit(3))) ;; let e23 = Plus(Times(IntLit(4),Minus(IntLit(1),IntLit(10))),IntLit(5)) ;; let e24 = Div(Times(e22,e23),IntLit(0)) ;;
亀山 幸義