OCamlとは
OCamlとはML(meta Language)系の関数型言語です。関数型言語には関数を値のように扱え、計算が数学のような関数で結果が引数に依存するといった特徴があります。つまり、関数を変数に代入したり、引数として渡すことが可能です。そして、OCamlではオブジェクト指向プログラミングもサポートされています。
開発環境
以下のサイトを参考にするといいと思います。
http://www.fos.kuis.kyoto-u.ac.jp/~igarashi/class/pl/setup.html
次にWSLで以下のコマンドを実行してutopをインストールしてください。
opam install core utop
これによって、WSLでocamlとコマンドを打つとocamlの対話モードが起動し、対話的にocamlを実行することができます。
(ちなみに、ファイルに書いたコードを実行するにはそのディレクトリ直下で「#use "ファイル名.ml";;」で実行できる )
文法
OCamlでは入力された式を値に評価することでプログラムの実行が進んでいきます。例えば対話モードで以下を実行すると、
# 1 + 2 * 3;;
次が返ってきます。
- : int = 7
; は電卓の=キーのようなもので,式の入力の終わりを告げるための記号です.結果として、数式の計算結果(値と呼ぶ) 7 が表示されています。int は式の型を示しています。
# 1 < 10;;
- : bool = true
# 1 < 10 && 3 < 1;;
- : bool = false
このように比較演算子も使うことができ、bool型で表されます。基本的なデータ型としては他にも浮動小数点のfloat型や文字列のstring型があります。
変数
OCamlでは変数は次のように定義します。
# let pi = 3.14;;
val pi : float = 3.14
piはfloat型で、valは値を表す名前が宣言されたことを表しています。これによって、これ以降はpiを3.14で束縛することができました。
関数
関数型言語において、関数は中心的な存在です。「let <関数名> <引数名> = <式>」の形で定義することができます。
# let pow x = x * x;;
val pow : int -> int = <fun>
定義したpow関数は累乗を計算してくれます。
# pow 4;;
- : int = 16
引数は複数個でも定義できます。
# let area x y = x * y;;
val area : int -> int -> int = <fun>
# area 4 8;;
- : int = 32
また引数をfloat型で扱うには以下のようになります。
# let circle r = pi *. r *. r;;
val area : float -> float = <fun>
# circle 4.0;;
- : float = 50.24
関数のスコープについて
OCamlでは同じ変数名を使ったとしても、それは代入にはなりません。
# let pi = 10.0;;
val pi : float = 10.
# circle 4.0;;
- : float = 50.24
変数を関数に反映させるには再定義してあげる必要があります。
# let pi = 10.0;;
val pi : float = 10.
# let circle r = pi *. r *. r;;
val area : float -> float = <fun>
# circle 4.0;;
- : float = 160.
このように OCaml では、変数の使用とそれに対応する関数の定義は,使用箇所から上に辿って一番近いものが適用されます。
条件分岐
if文は「if <条件式> then <式1> else <式2>」の形で定義できます。
# let max a b = if a > b then a else b;;
val max : 'a -> 'a -> 'a = <fun>
# max 10 3;;
- : int = 10
# max 3.5 13.0;;
- : float = 13.
再帰関数
再帰関数は,MLプログラム中で繰り返し計算(ループ)を表現する最も一般的な方法で
す。定義するにはletではなく、let recと書きます。
# let rec fact n = if n = 0 then 1 else n * fact (n - 1);;
val fact : int -> int = <fun>
# fact 5;;
- : int = 120
階乗の計算を行ってくれるfact関数を定義しました。ここで、もしif文の中でn = 0という条件がなかった場合どうなるでしょうか?
# let rec fact n = fact n * (n-1);;
val fact : int -> int = <fun>
# fact 3;;
Stack overflow during evaluation (looping recursion?).
このように計算結果は出力されず、メモリが大きくなりすぎたoverflowエラーとなってしまいます。このような計算が止まらず、エラーとなってしまう状態を無限ループといいます。無限ループを避けるためには以下の2点に気を付けることが重要です。
- 再帰関数fを展開しても f が出てこない場合を作ること.(factで言うとif n = 0 then 1の部分)
- 再帰関数fを展開したあとに出てくる f の呼び出しは,元の f の呼び出しよりも(何らかの意味で)小さいこと.
合成関数
関数f、gを受け取り、合成関数f ◦ gを返す関数についてです。composeを使います。
# let compose f g x -> g (f x);;
- : 'a -> ('b -> 'c) -> ('c -> 'd) -> 'b -> 'd = <fun>
composeを使って、次のような定義もできます。
# let pow x = x * x;;
# let add10 x = x + 10;;
# let add10_pow = compose add10 pow;;
# add10_pow 3;;
- : int = 169
関数add10_powでは書いてある順番通りに最初にadd10が適用されて、次にpowが適用されます。
上のコードを数式で書くとこんな感じです。
pow(x) = x * x
add10(x) = x + 10
add10_pow(x) = pow(add10(x))
add10_pow(3) = pow(add10(3)) = (3 + 10) * (3 + 10) = 169