2-3. 数式の処理

この実験で最初に作る処理系は、 前節のexp型のデータ(整数の定数と、足し算・かけ算からなる式)に対して、 その値を計算するものである。(現段階では、変数は含まない。)
(* 処理対象となる式の型の定義 *)
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" という名前をつける必要がない」というのが理由である。 「つけてもよいが、必要ない」場合に、 そのことが(プログラムを読んでいる人にとって)わかるようにすると、 プログラムの意味が理解しやすいため、わざわざ "_" を使うのである。

ここまで読むと、関数型プログラマとは変なことにこだわる人種と思うかもしれない。 確かにそうである。 しかし、プログラムを後から読む人(自分自身かもしれない)のことを考えると、 このように細部にまでこだわって美しく書くことが大事である。 プログラムを美しく書くこと--そう、それが実験の本当のテーマである。

課題 2-3.


トップ, 前へ, 次へ.

亀山 幸義