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 式2
の
in 式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 文の場合で述べている理由から、式1 も unit
型とする必要が
あります。
何故省略した場合は unit
型になるのでしょうか?
それは bool値式
が false
と評価された場合、
評価すべき式が無いためです。
match 式0 with パターン1 -> 式1 | ... | パターンn -> 式n
データ構造のパターンマッチングを実現する構文です。C 言語で言う
switch
文に似ていますが、より強力なものです。
式0 の値を、パターン1 から パターンn
のうち、マッチしたパターンに対応する式を評価します。
if 文の場合と同様、条件によって match 式全体の型が変化しては困るので、
式1〜式n は同じ型でなければなりません。
注意すべき点は、複数のパターンにマッチする事が可能な場合、どの
式が評価されるかは未定義であると言う事です
(書いた順番通りにはなるとは限らないと言う事)。特例として、
アンダーバー (_
) と言うパターンは、
他の全てにマッチしなかったら、と言う意味のパターンとなります。
パターンには様々な形があり、全部述べる事はしませんが、データ構造について 良くわかってくれば、だいたいわかるような物になってきます。
関数を定義する時に、引数が一つである関数値を生成する
function
を使った場合は似たような
構文で引数に関してパターンマッチを行なう事もできます。
これらの構文は 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
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
型であるべきです。