OCaml プログラミング入門


予約語

and         as          assert      asr         begin       class
closed      constraint  do          done        downto      else
end         exception   external    false       for         fun
function    functor     if          in          include     inherit
land        lazy        let         lor         lsl         lsr
lxor        match       method      mod         module      mutable
new         of          open        or          parser      private
rec         sig         struct      then        to          true
try         type        val         virtual     when        while
with

基本的な構文

ここでは式に関する重要な構文を説明します。

let 変数名 = 式

大域変数を定義します (と言うと語弊があるかもしれませんが...)。 意味としては、次の let 変数名 = 式1 in 式2in 式2 以降を省略したものと考えて良いかと思います。 OCaml では変数も関数も値としてほぼ同じに扱え、 関数もこの構文で定義します (関数型の値として定義すると言うだけの事)。 プログラムが複数のファイル(モジュール)から構成される時、このような 変数や値がモジュールの外から見えるようになります。

これ以降の構文は、全て「式」を表します。「式」と言うのは、 特定の値を表現していて、「評価」する事でその値を得る事ができます。 関数型言語は「式」の評価を通して計算が進みます。

let 変数名 = 式1 in 式2

2 における局所変数の定義です。 式1 を評価して、その値で変数名を束縛した上で、 式2 を評価します。 順番に並べると言う事で複数の局所変数を定義できます。 (局所変数とは、この場合、変数名が in 以降の式2でしか、式1 で評価された 値が束縛されていないと言う事です。)

let x = ... in
let y = ... in
  x + y

変数が関数型であれば、外からは見えない局所関数と言う事になります。

let rec 変数名 = 式

上述の let 変数名 = 式 と基本的には一緒ですが、 上の定義では、定義される変数名と同じ変数名が式の中にあっても、 別のものとして扱われます。この場合は、同じものとして扱われます。

再帰関数を定義する時に使います。 良く使われる例は階乗 (例: 5! = 5 * 4 * 3 * 2 * 1 = 120) の計算でしょうか。 では、階乗の関数 factを定義してみましょう。

let rec fact x = 
  if (x <= 1) then 1 else (x * fact (x-1))

もちろん普通の型に対しても使えます

# type hoge = { next: hoge };;
type hoge = { next : hoge; } 
# let rec x = { next = x };;
val x : hoge =
  {next =
    {next =
      {next =
        {next =
          {next = .... (略)

if bool型式 then 式1 else 式2

条件によって、結果となる値を変えたい時に使います。 この式は、bool 型式が、評価の結果 true であれば 式1 を評価し、 false であれば 式2 を評価します。 当然 if ... then ... else ... 式の表わす値の型は、 式1 の型、もしくは 式2 の型のいずれかになりますが、 条件によって型が変化したら困るので、式1 の型 と 式2 の型は同じでなければなりません。

if bool型式 then unit型式

C 言語のように、else 以下を省略した構文です。 しかし、式2を省略した場合はその型が unit 型 である事となり、 完全な if 文の場合で述べている理由から、式1unit 型とする必要が あります。 何故省略した場合は unit 型になるのでしょうか? それは bool値式false と評価された場合、 評価すべき式が無いためです。

match 式0 with パターン1 -> 式1 | ... | パターンn -> 式n

データ構造のパターンマッチングを実現する構文です。C 言語で言う switch 文に似ていますが、より強力なものです。 式0 の値を、パターン1 から パターンn のうち、マッチしたパターンに対応する式を評価します。 if 文の場合と同様、条件によって match 式全体の型が変化しては困るので、 式1〜式n は同じ型でなければなりません。 注意すべき点は、複数のパターンにマッチする事が可能な場合、どの 式が評価されるかは未定義であると言う事です (書いた順番通りにはなるとは限らないと言う事)。特例として、 アンダーバー (_) と言うパターンは、 他の全てにマッチしなかったら、と言う意味のパターンとなります。

パターンには様々な形があり、全部述べる事はしませんが、データ構造について 良くわかってくれば、だいたいわかるような物になってきます。

関数を定義する時に、引数が一つである関数値を生成する function を使った場合は似たような 構文で引数に関してパターンマッチを行なう事もできます。

function パターン1 -> 式1 | ... | パターンn -> 式n

これらの構文は OCaml (や 他の ML) で最も便利なものの一つで、この構文のおか げで C 言語等に比べてソースを凄まじく短かくする事ができ、可読性が増している と言えるでしょう。

raise 例外

構文というよりも関数なのかな。 例外を投げます。投げられた例外は、次の try ... with 式で 捕えられてなんらかの評価が行なわれるか、最後まで捕えられない 場合はプログラムが終了します。 raise を関数として見ると、例外が投げられた時点で式の評価を やめて結果が無い(型が他に影響しない)ので、 raise の型は exn -> 'a となっています。

try 式0 with 例外のパターン1 -> 式1 | ... | 例外のパターンn -> 式n

例外処理の構文です。C++ の try 〜 catch と同じようなものです。 まず 0 が評価され、その中で捕えられな かった例外が生じた場合、その例外が with 以降のパターンにマッチにする 式が評価されます。 例外が生じない場合はそのまま0の評価結果が 式の評価結果になります。 この事から、式0 ... 式nの型は全て等しくないとい けません。

let div x y =
  try
    string_of_int (x / y)
  with Division_by_zero -> "0 で割るなゴルァ!";;

命令型言語風構文

OCaml では、命令型言語のような構文も用意されています。 これらを使えば、C 言語などを使ってた人であれば、OCaml で 簡単にプログラムを書けるようになるとは思いますが、関数型 言語的プログラミングスタイルに慣れるまでは、あまり使用し ない方が良いかと思います。 でないと、関数型言語的プログラミングスタイルの旨味がなか なか理解できなくなってしまう可能性があるからです。

関数型言語的プログラミングスタイルに慣れるためには、 修行だと思って、以下の構文を 使わずにいかに綺麗に書けるか を考えながらプログラムすると良いと思います。

1; 式2

1 を評価して、式2 を評価しますが、 式1 の結果は捨てられて、式2 の値を 表します。式1 の値は捨てられるので、 unit 型である事が望ましいです。 (unit 型でなくとも、コンパイラはワーニングを出すだけで、コンパイルは通ります)

for 変数名 = int型式1 to int型式2 do 式3 done

C 言語と似たような for 文です。 変数名に int型式1 から int型式2 までの値を順番に(昇順)束縛していき、 その元で 式3 をその回数だけ評価します。

# for i = 0 to 4 do (Printf.printf "%d\n" i) done;;
0
1
2
3
4
- : unit = ()

to のかわりに downto と書くと、 降順でループをまわしてくれます。

二つの int 型式の値によっては、中身が一度も 評価されない事があり、そのような場合には for 文全体の 値が未定義になってしまうと言う事から、for 文の結果は unit 型と定義されています。ですので、式3 の値は 捨てられてしまうため、unit 型であるべきです。

while bool型式 do 式2 done

while 文です。bool 型式が true と評価されるうちは、式2 を評価します。 bool型式が最初から false であったり、 常に true である場合に、while 文全体の値が未定義になってしまうと言う事から、 while 文の結果は unit 型と定義されています。ですので、式2 の 値は捨てられてしまうため、unit 型であるべきです。