OCaml(オーキャムル、オーキャメル、旧称: Objective Caml)は、汎用、高水準、マルチパラダイムのプログラミング言語であり、MLの方言にオブジェクト指向の機能を追加したものである。1996年にグザヴィエ・ルロワ、、、、Ascánder Suárezらによって開発された。

OCamlのツールチェーンには、対話型のトップレベルインタプリタ、バイトコードコンパイラ、最適化を行うネイティブコードコンパイラ、リバーシブルなデバッガ、パッケージ管理システム(OPAM)、および組み合わせ可能なビルドシステム(Dune)が含まれている。OCamlは当初、自動定理証明の文脈で開発され、静的解析や形式手法のソフトウェアで利用されている。これらの分野を超えて、、Web開発、特定の金融ユーティリティなど、様々なアプリケーション領域で活用されている。

名称の「CAML」は元々「Categorical Abstract Machine Language(圏論的抽象機械言語)」の頭字語であったが、OCamlではこの抽象機械を省略している。OCamlはフランス国立情報学自動制御研究所(Inria)によって管理および主に保守されているフリーかつオープンソースのプロジェクトである。2000年代初頭、OCamlの要素は多くの言語、特にF#やScalaなどに採用された。

設計思想 ML派生の言語は、その静的な型システムと型推論コンパイラで最もよく知られている。OCamlは、ML風の型システムの下で関数型、命令型、およびオブジェクト指向のパラダイムを統合している。そのため、プログラマはOCamlを使用するにあたって、純粋関数型言語のパラダイムに精通している必要はない。

プログラマに対し、その静的型システムの制約内で作業することを要求することにより、OCamlは動的型付け言語によく見られる型に関連した実行時の多くの問題を排除する。また、OCamlの型推論コンパイラは、多くの静的型付け言語で必要とされる手動による型アノテーションの必要性を大幅に減らす。たとえば、変数のデータ型や関数のは、コード内の変数や他の値に適用される演算子や他の関数から推論できるため、JavaやC#などの言語のように明示的に宣言する必要がない場合が多い。OCamlの型システムを効果的に使用するには、プログラマに一定の熟練が求められることがあるが、この規律は信頼性の高い高性能なソフトウェアという形で報われる。

学術界に起源を持つ他の言語と比較して、OCamlを最も特徴づけているのはパフォーマンスへの重点かもしれない。その静的型システムは実行時の型の不一致を防ぐため、動的型付け言語のパフォーマンスの負担となる実行時の型チェックや安全性チェックを不要にしつつも、をオフにした場合や、シリアライズのような型安全でない機能を使用した場合を除いて、実行時の安全性を保証する。これらは極めて稀であるため、実際には避けることが十分に可能である。

型チェックのオーバーヘッドを別としても、関数型言語は一般に、Funarg問題などの問題により、効率的な機械語コードにコンパイルするのが難しい。標準的なループ、レジスタ、命令の最適化に加えて、OCamlのは静的プログラム解析手法を用いて値のボクシング(boxing)とクロージャの割り当てを最適化し、関数型プログラミングの構造を多用した場合でも、生成されるコードのパフォーマンスを最大化するよう支援する。

グザヴィエ・ルロワは「OCamlは、まともなCコンパイラの少なくとも50%のパフォーマンスを発揮する」と述べているがLinux Weekly News.、直接的な比較は不可能である。OCamlの標準ライブラリのいくつかの関数は、他の言語の標準ライブラリにおける同等の関数よりも高速なアルゴリズムで実装されている。たとえば、OCaml標準ライブラリにおける集合の和集合の実装は、OCamlの実装が集合の不変性(immutability)を利用して入力集合の一部を出力で再利用できるため(永続データ構造を参照)、理論的には命令型言語(C++、Javaなど)の標準ライブラリにおける同等の関数よりも漸近的に高速である。

歴史

ML(Meta Language)の開発 1970年代から1980年代にかけて、イギリスの計算機科学者でありチューリング賞受賞者のロビン・ミルナーは、エディンバラ大学ので研究を行っていた。ミルナーらは定理証明器の開発に取り組んでおり、これらは歴史的にLispなどの言語で開発されていた。ミルナーは、定理証明器が証明ではないものを組み合わせることで証明が有効であると主張しようとする問題に繰り返し直面した。その結果、彼は自身の(LCF)のためのメタ言語(meta language)を開発することになった。これは多相型システムによって、プログラマが有効な証明のみを構築できるようにする言語であった。MLは、異なるマシンでのLCFの使用を簡素化するためにコンパイラへと変えられ、1980年代までにはそれ自体が完全なシステムとなった。MLは最終的にOCamlの基盤として機能することになる。

1980年代初頭、フランス国立情報学自動制御研究所(Inria)のFormelチームがMLに関心を持つきっかけとなるいくつかの進展があった。オックスフォード大学の研究教授であったは、自身の「関数型抽象機械(functional abstract machine)」を使用してMLのより高速な実装を開発し、ロビン・ミルナーは様々な実装間の相違を避けるためにMLの新しい定義を提案した。同時に、パリ・ディドロ大学(現・パリ・シテ大学)の上級研究員であったピエール=ルイ・キュリアン(Pierre-Louis Curien)は圏論的コンビネータ(categorical combinators)の計算を開発し、それをラムダ計算に結びつけた。これにより「(CAM)」の定義が導かれた。パリ・ディドロ大学の研究者ガイ・クジノー(Guy Cousineau)は、これがMLのコンパイル手法として適用できることを認識した。

初期の実装 は、率いるInriaのFormelチームによって最初に設計および開発された。Camlの最初の実装は1987年に作成され、1992年までさらに開発が続けられた。開発はAscánder Suárezによって主導されていたが、1988年に彼が去った後はピエール・ワイスとミシェル・モーニー(Michel Mauny)が開発を引き継いだ。

Caml Light 1990年から1991年にかけて、グザヴィエ・ルロワはC言語で書かれたバイトコードインタプリタに基づくの新しい実装を設計した。これに加えて、はこの実装のために、シーケンシャルなガベージコレクションとして知られるメモリ管理システムを記述した。として知られるこの新しい実装は、古いCamlの実装を置き換え、小型のデスクトップマシンで動作した。その後数年で、ミシェル・モーニーの構文操作ツールなどのライブラリが登場し、教育や研究チームでのCamlの使用を促進するのに役立った。

Caml Special Light 1995年、グザヴィエ・ルロワはの改良版であるCaml Special Lightをリリースした。バイトコードコンパイラに最適化されたネイティブコードコンパイラが追加され、これによりパフォーマンスがC++などの主要言語と同等レベルにまで大幅に向上した。また、ルロワはStandard MLのモジュールシステムに触発された高水準モジュールシステムを設計した。これにより、抽象化とパラメータ化のための強力な機能が提供され、より大規模なプログラムの構築が容易になった。

Objective Caml ディディエ・レミとジェローム・ヴイヨンは、オブジェクトとクラスのための表現力豊かな型システムを設計し、それがCaml Special Lightに統合された。これにより、1996年に初めてリリースされたObjective Caml言語が誕生し、後に2011年にOCamlへと改名された。このオブジェクトシステムは、C++やJavaなどの言語では型の不健全性(unsoundness)を引き起こしたり、実行時のチェックを必要としたりするような、普及している多くのオブジェクト指向のイディオムを、静的かつ型安全な方法でサポートしていた。2000年にジャック・ガリグ(Jacques Garrigue)は、多相メソッド、バリアント、ラベル付きおよびオプションの引数など、複数の新機能でObjective Camlを拡張した。

継続的な開発 増大するOCamlの商業的および学術的なコードベースをサポートするため、過去20年間にわたり言語の改善が段階的に追加されてきた。2012年のOCaml 4.0のリリースでは、言語の柔軟性を高めるために、一般化代数的データ型(GADT)とファーストクラス・モジュールが追加された。2022年のOCaml 5.0.0リリースは言語ランタイムの完全な書き直しであり、グローバルインタプリタロック(グローバルGCロック)が削除され、限定継続(delimited continuations)を介した(effect handlers)が追加された。これらの変更により、それぞれ共有メモリ並列処理と、カラーブラインド並行性(color-blind concurrency)のサポートが可能になる。

OCamlの開発は2005年までInriaのCristalチーム内で行われていたが、その後Galliumチームに引き継がれた。続いて、2019年にGalliumはCambiumチームに引き継がれた。2023年現在、コンパイラ配布のコア開発者は様々な組織からの23名で構成されており、幅広いOCamlツーリングおよびパッケージング・エコシステムには41名の開発者がいる。2023年、OCamlコンパイラは(ACM SIGPLAN Programming Languages Software Award)を受賞した。

特徴 OCamlは、静的な型システム、型推論、、末尾再帰、パターンマッチング、ファーストクラスの静的クロージャ、ファンクタ(パラメータ化されたモジュール)、例外処理、(effect handling)、およびインクリメンタルな世代別ガベージコレクションを特徴としている。

OCamlは、汎用言語においてMLスタイルの型推論をオブジェクトシステムに拡張していることで知られている。これにより、宣言された継承に関係なく、メソッドのシグネチャが互換であればオブジェクト型に互換性があるという、(structural subtyping)が可能になる(これは静的型付け言語では珍しい機能である)。

CやFORTRANと互換性のある形式の効率的な数値配列(array)の言語サポートを含む、C言語のプリミティブをリンクするためのForeign function interface(FFI)が提供されている。OCamlは、Cの「main」プログラムにリンクできるOCaml関数のライブラリの作成もサポートしており、OCamlの知識がない、またはインストールしていないCプログラマにもOCamlライブラリを配布することができる。

OCamlには、言語の不可分な一部としてのマクロシステム(メタプログラミング)、すなわちプリプロセッサによる前処理の組み込みサポートはないが、OCamlプラットフォームは公式に、そのようなプリプロセッサを記述するためのライブラリをサポートしている。これらには2つの種類がある。1つは(C言語のように)ソースコードレベルで機能するもの、もう1つは抽象構文木(AST)レベルで機能するものである。後者は「PPX」(Pre-Processor eXtensionの略)と呼ばれ、推奨されている方式である。

OCamlのディストリビューションには以下が含まれる: ocamllexおよびocamlyaccと呼ばれる字句解析および構文解析ツール エラーを調査するためのバックワード・ステップ(巻き戻し実行)をサポートするデバッガ ドキュメント・ジェネレータ パフォーマンスを測定するためのプロファイラ 多くの汎用ライブラリ

ネイティブコードコンパイラは、UNIX、Microsoft Windows、Apple macOSを含む多くのプラットフォームで利用できる。主要なアーキテクチャに対するネイティブコード生成のサポートにより、移植性が実現されている:

x86-64 (AMD64)、RISC-V、およびARM64(OCaml 5.0.0以降) IBM Z(OCaml 5.0.0以前、およびOCaml 5.1.0で復活) Power(OCaml 5.0.0以前、OCaml 5.2.0で再登場予定) IA-32およびARM(OCaml 5.0.0以前) SPARC(OCaml 4.06.0以前) DEC Alpha、HPPA、IA64、およびMIPS(OCaml 4.00.0以前)

バイトコードコンパイラは、ネイティブコード生成が利用できない場合でも、Cコンパイラさえあれば、任意の32ビットまたは64ビットアーキテクチャでの動作をサポートする。

OCamlのバイトコードおよびネイティブコードプログラムは、プリエンプティブなコンテキストスイッチを備えたマルチスレッドスタイルで記述できる。同じドメインドメイン(domain)はOCamlにおける並行処理の単位であり、通常はCPUのコアに対応する。内のOCamlスレッドは、タイムシェアリングによってのみ実行される。ただし、1つのOCamlプログラムには複数のドメインを含めることができる。

その他の派生実装および方言 Caml Light 前述の通り、OCamlの前身である実装。正確には名前だけでなく、新しい手法で再実装されたものであり、OCamlよりCaml Lightのほうが古くからある。現在もCaml Lightとして配布され続けている。

MinCaml MinCamlは、ペンシルベニア大学(当時)の住井英二郎がOCamlで実装した、Caml似のMLの小型版である。同作者により、コンパイラがOCaml自身で書かれている。MinCamlは、2004年度の未踏ソフトウェア創造事業に採択された。

MinCamlコンパイラは教育目的での利用を主眼としている。わずか2000行前後のコードで書かれており、実装されている機能はMLのサブセットである。バックエンドはSPARCとx86に対応しており、ある程度の学習をすれば比較的容易に改造を行うことができる(有志によってPowerPC用に出力できるバージョンも提供されている。バックエンドをLLVMに置き換えた例も報告されているhttps://mzp.hatenablog.com/entry/2013/05/08/214712)。実際に東京大学理学部情報科学科などで教育目的に利用され、国内におけるOCamlおよび関数型言語の普及と理解に一定の役割を果たしている。

速攻MinCamlコンパイラ概説 - MinCamlの配布・解説(SourceForge.net)

Moscow ML CamlやOCamlのような方言ではなく、SML(Standard ML)の処理系の実装にCaml Lightを利用している。完全なSMLを実装する。

その他 OchaCamlなど、研究用の改造のベースとして、規模の大きくなったOCamlではなくCaml(Caml Light)を利用する例がみられる。

派生言語 MetaOCaml MetaOCaml は、実行中に新しい機械語コードのインクリメンタルなコンパイルを可能にする、OCamlの拡張である。いくつかの状況下では、多段階プログラミングを使用することで大幅な高速化が可能になる。これは、通常のコンパイル時よりも実行時の方が処理するデータに関する詳細な情報が得られるため、インクリメンタルコンパイラが条件チェックなどの多くのケースを最適化して排除できるためである。

例として、コンパイル時に何らかの冪乗関数x -> x^nが頻繁に必要になることがわかっているが、nの値はランタイム時にしかわからない場合、MetaOCamlでは2段階の冪乗関数を使用できる。

let rec power n x = if n = 0 then .<1>. else if even n then sqr (power (n/2) x) else .<.~x *. .~(power (n - 1) x)>.

実行時にnが判明するとすぐに、特化された非常に高速な冪乗関数を作成できる。

.<fun x -> .~(power 5 .<x>.)>.

その結果、以下のようになる。

fun x_1 -> (x_1 * let y_3 = let y_2 = (x_1 * 1) in (y_2 * y_2) in (y_3 * y_3))

新しい関数は自動的にコンパイルされる。

その他の派生言語 F# - OCamlをベースにした.NETフレームワーク言語。 - 並行および分散プログラムを開発するための構造を統合した言語。 - Facebookで作成された、ネイティブコードとJavaScriptの両方にコンパイルできる代替のOCaml構文およびツールチェーン。

プログラム例 以下の例は、プログラム自体としてはMLと比べ特別なものでもないし、オブジェクト指向を活用したものでもないが、OCamlを含むCamlでは旧来のMLやStandard MLからの記法や演算子や名前の変更が多く、簡単なプログラムでもそのままではエラーになるものが多いので、ここではOCamlのコードを示す。

特徴として、型推論の活用により、多くの場合に型の宣言が必要なく、一部の静的型付き言語にありがちな煩雑さがないことが挙げられる。

OCamlのコード・スニペットは、トップレベルの対話型環境(REPL)に入力することで最も簡単に学習できる。これは、結果または定義された式の推論された型を出力する対話型のOCamlセッションである。OCamlのトップレベルは、単にプログラムを実行することで起動する。

$ ocaml OCaml version 5.4.0 Enter #help;; for help.

その後、プロンプト "#" にコードを入力できる。例えば、を計算するには:

# 1 + 2 * 3;; - : int = 7

OCamlは式の型を "int" (マシン精度の整数型)と推論し、"7" という結果を返す。

Hello World Hello world の例を示す。以下のプログラムhello.mlは、

print_endline "Hello world!";;

直接実行することができる。 $ ocaml hello.ml

バイトコード実行可能ファイルにコンパイルする場合: $ ocamlc hello.ml -o hello

最適化されたネイティブコード実行可能ファイルにコンパイルする場合: $ ocamlopt hello.ml -o hello

実行結果: $ ./hello Hello world! $

ocamlcへの最初の引数である "hello.ml" はコンパイルするソースファイルを指定し、"-o hello" フラグは出力ファイルを指定する。

Option型 OCamlにおけるoption型コンストラクタは、HaskellのMaybe型に似ており、与えられたデータ型を拡張して、そのデータ型の何らかの値(Some)を返すか、何も返さない(None)ようにする。これは値が存在するかしないかを表現するために使用される。

# Some 42;; - : int option = Some 42 # None;; - : 'a option = None

以下の関数は、オプションの内部に整数があればそれを抽出し文字列に変換し、なければ空の文字列を返す例である。

let extract o = match o with | Some i -> string_of_int i | None -> "";; # extract (Some 42);; - : string = "42" # extract None;; - : string = ""

リストの合計 リストはOCamlの基本的なデータ型の1つである。以下のコード例では、整数のリストを引数に取る再帰関数 sum を定義している。関数が再帰的であることを示すキーワードrecに注目してほしい。この関数は与えられた整数のリストを再帰的に反復処理し、要素の合計を提供する。matchステートメントはC言語のswitch文と似ている部分があるが、はるかに汎用性が高い。

let rec sum integers = (* キーワード rec は「再帰的」を意味する *) match integers with | [] -> 0 (* integers が空のリスト [] の場合は 0 を返す *) | first :: rest -> first + sum rest;; (* integers が空でないリストの場合は再帰呼び出し。 first はリストの最初の要素、 rest は残りの要素のリスト([] の場合もある) *) # sum [1;2;3;4;5];; - : int = 15

別の方法として、リストに対して機能する標準的なを使用することもできる。

let sum integers = List.fold_left (fun accumulator x -> accumulator + x) 0 integers;; # sum [1;2;3;4;5];; - : int = 15

無名関数は単なる+演算子の適用であるため、以下のように短縮できる。

let sum integers = List.fold_left (+) 0 integers

さらに、を利用することでリストの引数を省略することもできる。

let sum = List.fold_left (+) 0

クイックソート OCamlは再帰的なアルゴリズムを簡潔に表現するのに適している。以下のコード例は、リストを昇順にソートするクイックソートに似たアルゴリズムを実装している。Haskellなどにも見られるパターンマッチの機能がここでも使われている。

let rec qsort = function | [] -> [] | pivot :: rest -> let is_less x = x < pivot in let left, right = List.partition is_less rest in qsort left @ [pivot] @ qsort right

または、演算子の部分適用を使用する。

let rec qsort = function | [] -> [] | pivot :: rest -> let is_less = (>=) pivot in let left, right = List.partition is_less rest in qsort left @ [pivot] @ qsort right

誕生日のパラドックス 以下のプログラムは、部屋にいる人全員の誕生日が完全に異なる確率が50%未満になる最小の人数を計算する(誕生日のパラドックス。1人の場合は確率が365/365(100%)、2人の場合は364/365、3人の場合は364/365 × 363/365…となる)(答え = 23)。

let year_size = 365.

let rec birthday_paradox prob people = let prob = (year_size -. float people) /. year_size *. prob in if prob < 0.5 then Printf.printf "answer = %d\n" (people+1) else birthday_paradox prob (people+1)

birthday_paradox 1.0 1

チャーチ数 以下は、ラムダ計算の教科書などに見られる、自然数ののコード例である。後者(succ)および加算(add)が定義されている。チャーチ数nは、関数fと値xを受け取り、xに正確にn回fを適用する高階関数である。チャーチ数を関数値から文字列に変換するために、入力に文字列"S"を先頭に追加する関数と、定数文字列"0"を渡す。以下の例では、5のチャーチ数として"SSSSS0"を出力する。

let zero f x = x let succ n f x = f (n f x) let one = succ zero let two = succ (succ zero) let add n1 n2 f x = n1 f (n2 f x) let to_string n = n (fun k -> "S" ^ k) "0" let church_5 = to_string (add (succ two) two) ;; print_endline church_5;

任意精度階乗関数(ライブラリ) 様々なライブラリにOCamlから直接アクセスできる。たとえば、OCamlには任意精度演算のための組み込みライブラリがある。階乗関数は非常に急速に増加するため、すぐにマシン精度の数値(通常は32ビットまたは64ビット)をオーバーフローする。したがって、階乗は任意精度演算の適切な候補となる。

OCamlでは、Numモジュール(現在はZArithモジュールに置き換えられている)が任意精度演算を提供しており、次のように入力することで実行中のトップレベルに読み込むことができる。

# #use "topfind";; # #require "num";; # open Num;;

すると、任意精度数値演算子=/, */, -/を使用して階乗関数を記述できる。

# let rec fact n = if n =/ Int 0 then Int 1 else n */ fact(n -/ Int 1);; val fact : Num.num -> Num.num = <fun>

この関数は、120!のようなはるかに大きな階乗を計算できる。

# string_of_num (fact (Int 120));; - : string = "6689502913449127057588118054090372586752746333138029810295671352301633 55724496298936687416527198498130815763789321409055253440858940812185989 8481114389650005964960521256960000000000000000000000000000"

三角形(グラフィックス) 以下のプログラムは、OpenGLを使用して2Dで回転する三角形を描画する。

let () = ignore (Glut.init Sys.argv); Glut.initDisplayMode ~double_buffer:true (); ignore (Glut.createWindow ~title:"OpenGL Demo"); let angle t = 10. *. t *. t in let render () = GlClear.clear [ `color ]; GlMat.load_identity (); GlMat.rotate ~angle: (angle (Sys.time ())) ~z:1. (); GlDraw.begins `triangles; List.iter GlDraw.vertex2 [-1., -1.; 0., 1.; 1., -1.]; GlDraw.ends (); Glut.swapBuffers () in