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 という関数が再帰関数であるため、rec という キーワードを使っていることに注意せよ。 試しに、rec をはずすと、どのようなエラーに なるか試してみよ。

もう1つのポイントは、match ... with という構文で、 式 e に対するパターンマッチを行い、その種類に応じて 異なる処理をしている。このパターンマッチは OCaml でプログラムを書くと きに極めて重要な機能なので、この段階で色々試してしっかりマスタしてほしい。

なお、上記のインタープリタ (eval1) は、今後どんどん拡張していく予定 である。その際、「構文上は式であるが、まだ、その処理方法を 記述していない(自分の作っているインタープリタがその処理方法を知らない)」 という状態が発生する。 そのような場合に、エラーが発生するようにしておこう。 このためには、OCaml の例外(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"
すると、まだ処理方法を記述していない式を eval1 に与えると "unknown expression"という表示を与えるエラー が発生し、処理がただちに停止する。(なお、正確には「エラー」ではなく 「Failure という名前の例外」を発生するのであるが、ここでは その詳細を気にする必要はない。)

なお、failwith は常に、文字列を1つだけ引数としてとる。

また、ここで "_" (アンダーラインあるいはアンダースコア) は、 「どんな式とでもマッチするパターン」である。 変数 x も「どんな式とでもマッチするパターン」であるので, 上記のプログラムは、

  ...
  | x -> failwith "unknown expression"
と書いてあっても、まったく同じ機能である. つまり,この行の上に書いてあるパターンマッチが全部失敗したら, 必ず、この行でパターンマッチに成功して、failwith が実行される。 つまり、C言語やその他の言語にもある「defaultのケース」を表す表現である。

ところで、変数 x でもよいのに、なぜ "_" という読みにくい表現を使うのだろうか? 理由としては, この x は右辺では使わないので、わざわざ "x" という名前をつける必要がな い,ということである。「つけてもよいが,必要ない」場合に, そのことが(プログラムを読んでいる人にとって)わかりやすくするため, わざと,"_" を使うのである。

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

課題 3.


トップ, 前へ, 次へ.

亀山 幸義