OCaml
OCaml(オキャムル/オーキャムル)は、INRIA というフランスの国立の計算機科学の研究所でデザイン・開発された関数型言語で、証明支援系と呼ばれる、計算機によって数学的証明を行うためのソフトウェアを操作するための言語の研究から発展してきたものです。ML(Meta Language)という言語にオブジェクト指向を取り入れて開発されました。そのため、関数型言語とオブジェクト指向言語の特徴を兼ね備えており、また簡潔に信頼性の高いコードを書くことが可能です。ただOCamlを積極的に導入している一般企業は、今のところほとんどなく、大学や研究機関で使用されることが多いです。
MLについて
MLとは定理証明支援系において、数学の証明を記述するために誕生した言語です。MLのプログラムは「式」であり、その「式」の値を計算することを「式」を「評価する」と言います。MLは「式」を「評価する」ことでプログラムが実行されます。基本的にはOCamlも同じです。
特長
オブジェクト指向
最初に言った通り、OCamlは関数型以外にオブジェクト指向の特徴も持ち合わせます。例えば、文字列"Hello World\n"を返すメソッドgetと、その文字列を表示するメソッドprintを持つオブジェクトhelloは、以下のように定義できます。
let hello =
object (self)
method get = "Hello World\n"
method print = print_string self#get
end ;;
(* val hello : < get : string; print : unit > = <obj> *)
helloオブジェクトのprintメソッドは以下のようにして呼び出すことができます。
# hello#print ;;
Hello World
- : unit = ()
ただ、オブジェクト指向によって、破壊的操作もできてしまいます。また、OCamlではオブジェクト指向を意識したコードを書くことは少ないらしいです。
型推論
OCamlは強い静的型付け言語であり、コンパイル時に静的型チェックが実行されます。この強力な型システムを支えるのが型推論です。そこに書くことが期待されている型」をコンパイラが推論してくれます。例えば、与えられた数値に1を足す関数を考えます。
# fun x -> x + 1;;
- : int -> int = <fun>
ここで、事前にxはint型であると明示していません。このように、特に型の宣言をしなくても、「+」や「1」からxはint型で全体はint -> int型を返すということを推測してくれます。他にも、2つの引数をとって、足し算をしてくれる関数や、
# fun x y -> x + y;;
- : int -> int -> int = <fun>
文字列引数nameを受け取り,nameの前に定数文字列"hello "を付け加えて返す,という関数も以下のように肩を推論してくれます。
# fun name -> "hello" ^ name;;
- : string -> string = <fun>
こんなのもできます。
# fun x -> if x then 10 else 20;;
- : bool -> int = <fun>
逆に不適切な式について考えてみます。
# fun x -> if x then 1 else x;;
if式の型規則は if 直後の式は bool に等しく、then節,else節の型も等しい、というものであるから、xに割り当てられた型変数を'aとすると、int = 'a = bool ということが言えます。しかし、これは明らかに解くことができないので,この式は型エラーとなります。
このようにOCamlでは「式を与えられたら,その型を求める」型推論によって、煩雑な鷹宣言を省略することができます。
多相型システム
多相型(polymorphic type)とは、型変数を含む型で、その型の式がその型変数を任意の具体的な型に置き換えても正しく型付けされ評価できるような機能を備えたもののことです。ちなみに多相型ではない型を単相型(monomorphic type)といいます。
この説明だけでは分かりにくいと思うので、最も簡単な多相型を紹介します。
# let id x = x;;
val id : 'a -> 'a = <fun>
このid関数は受けとった引数をそのまま返す関数です。id の型は,int -> int であろうが,bool -> boolであろうが引数と返り値の型が揃っている限りなんでもよいことになります。そして、id関数の型推論で出てきた'aを型変数と呼び、’a の箇所にはどのような型も当てはめることができます。この’a -> ’a が多相型です。ちなみに、多相型の型変数に具体的な型を割り当て、多相型の具体的な例を作ることをインスタンス化と言い、作られた型の例のことをインスタンスと呼びます。例えば、
# id 3;;
- : int = 3
# id "hello";;
- : string = "hello"
# id 3.14;;
- : float = 3.14
これらがid関数のインスタンスになります。id 3はint -> int、id "hello"はstring -> string、id 3.14はfloat -> floatにインスタンス化されています。
他にも多相型の例として以下のものもあります。
# let compose f g x = f (g x);;
val compose : ('a -> 'b) -> ('c -> 'a) -> 'c -> 'b = <fun>
compose関数は関数であるfとg、それとxを引数として受け取り、それらを合成した結果を返してくれます。’a、’b、’cには自由に型を当てはめることができます。これらの方はさきほど説明した型推論によって、導き出されています。
高階関数
高階関数とは、「関数を他の関数への引数として渡す」「結果として関数を受け取る」などのことです。先ほどの多相型の話で出てきたcompose関数も高階関数の一つです。他に例を挙げると、
# let plus10 f = f 5 + 10;;
val plus10 : (int -> int) -> int = <fun>
# let square x = x * x;;
val square : int -> int = <fun>
# plus10 square;;
- : int = 35
ここではplus10関数が高階関数になります。square関数を受け取って、そこに5を手起床してプラス10した値を返しています。
パターンマッチング
パターンマッチングは ML の特徴の一つで、与えられたデータをパターンによって照合させるという便利な機能です。パターンマッチングはmatch式で行います。match 式の構文は以下の通りです。
match 式0 with
pattern_1 -> 式1
| pattern_2 -> 式2
...
| pattern_n -> 式n
パターンマッチングを使った簡単な例を示します。
# let isEmpty l =
match l with
[] - > true
| _ -> false;;
val isEmpty : 'a list -> bool = <fun>
isEmpyt関数は与えられたリストが空かどうかを判定してくれます。match式は上から順に与えられたデータを照合していきます。[]にマッチすれば、trueを、「_」はワイルドカードパターンと呼ばれる何にでもマッチしていいということを表す記号で、これに当てはまればfalseを返します。
実際に試してみると、
# isEmpty [1::2::3::[]];;
- : bool = false
# isEmpty [];;
- : bool = true
正しく判定できていることが分かります。
続いて、偶数かどうかを判定するisEven関数です。
let isEven x =
match (x mod 2) with
0 -> true
| 1 -> false;;
これを定義したとき、以下のような結果が返ってきます。
Warning 8: this pattern-matching is not exhaustive.
Here is an example of a case that is not matched:
2
val isEven : int -> bool = <fun>
これは「int 型の値は 0 や 1 や以外にもあるのに(例えば 2)、そのパターンの場合を処理する分岐が書かれていない」ことを警告しています。パターンマッチによって、全パターンを網羅できていないときに発生するメッセージで、この機能を使うときは非常に重要になります。しかし、今回はint型を2で割ったときは必ず0か1になることが分かり切っているので無視してしまって大丈夫です。気になるならワイルドカードを使って以下のように定義するといいと思います。
# let isEven x =
mat ch (x mod 2) with
0 -> true
| _ -> false;;
val isEven : int -> bool = <fun>
他にも再帰関数の中で、match式を組み込むこともできます。
(* リストの中身を全て足した結果を返す *)
# let rec add_list l =
match l with
[] -> 0
| h :: s -> h + add_list s;;
# add_list [1; 2; 3; 4; 5; 6; 7; 8; 9; 10];;
- : int = 55
(* 最大公約数 *)
# let rec gcd a b =
mat ch b with
0 - > a
| b -> gcd b (a mod b);;
val gcd : int -> int -> int = <fun>
# gcd 12 6;;
- : int = 6
# gcd 3213 945234;;
- : int = 153
ガベレージコレクション
メモリの管理を自動的に行ってくれます。そのため、CやC++の malloc/freeようなメモリ確保を明示する必要がなく、それに関するバグを抑えることができます。しかもOCamlのメモリ確保はかなり高速だそうです。
まとめ
ここまでOCamlの特長について、まとめてきました。これらのおかげで、OCamlは高い信頼性を誇るコードを簡潔に書くことができます。関数型言語はあまり世の中では一般的な言語ではありませんが、その概念を学ぶことは「正しいプログラム」について理解を深めることができます。
参考文献
Objective Caml 入門
オープンソースの言語/OCamlとは
第4回 関数型言語とオブジェクト指向,およびOCamlの"O"について
第12回 「型推論」の実装法
7-5. 単純な式に対する型推論
第14回 型=命題,プログラム=証明
OCaml.jp もっと詳しく