OCaml(/ oʊ ˈ k æ m əl / oh- KAM -əl、旧称Objective Caml)は、MLのCaml方言をオブジェクト指向機能で拡張した、汎用、高水準、マルチパラダイム プログラミング言語です。OCamlは1996年にXavier Leroy、Jérôme Vouillon、[5] Damien Doligez、Didier Rémy、[6] Ascánder Suárezらによって作成されました。
OCamlツールチェーンには、対話型トップレベルインタープリタ、バイトコード コンパイラ、最適化ネイティブコードコンパイラ、可逆デバッガ、パッケージマネージャ(OPAM)、そしてOCaml用のコンポーザブルビルドシステム(Dune)が含まれています。OCamlは当初、自動定理証明の文脈で開発され、静的解析や形式手法ソフトウェアで使用されています。これらの分野以外にも、システムプログラミング、Web開発、特定の金融ユーティリティなど、様々なアプリケーション分野で利用されています。
CAMLという略語は元々Categorical Abstract Machine Language(カテゴリ抽象機械言語)の略でしたが、OCamlではこの抽象機械が省略されています。[7] OCamlは、フランス国立情報科学・自動化研究所(Inria)が管理・保守を主に行う、フリーでオープンソースのソフトウェアプロジェクトです。2000年代初頭には、OCamlの要素が多くの言語、特にF#やScalaに採用されました。
ML派生言語は、静的型システムと型推論コンパイラで最もよく知られています。OCamlは、関数型プログラミング、命令型プログラミング、そしてオブジェクト指向プログラミングをML風の型システムの下に統合します。そのため、OCamlを使用するためにプログラマーが純粋関数型言語のパラダイムに高度な知識を持つ必要はありません。
OCaml では、プログラマが静的型システムの制約内で作業するように要求することで、動的型付け言語に伴う型関連の実行時問題の多くを排除しています。また、OCaml の型推論コンパイラにより、ほとんどの静的型付け言語で必要とされる手動による型注釈の必要性が大幅に軽減されます。たとえば、変数のデータ型や関数のシグネチャは、コード内の変数やその他の値に適用される演算子やその他の関数から推論できるため、JavaやC#などの言語のように明示的に宣言する必要は通常ありません。OCaml の型システムを効果的に使用するには、プログラマ側にある程度の高度な知識が要求されますが、この訓練は信頼性が高く、高性能なソフトウェアという形で報われます。
OCamlが学術界発祥の他の言語と最も大きく異なる点は、パフォーマンスへの重点でしょう。静的型システムは実行時の型の不一致を防ぎ、動的型付け言語のパフォーマンスを低下させる実行時の型・安全性チェックを不要にします。同時に、配列境界チェックがオフになっている場合や、シリアル化などの型安全でない機能が使用されている場合を除き、実行時の安全性も保証します。こうした機能は極めて稀なので、実際には回避することは十分に可能です。
型チェックのオーバーヘッドに加え、関数型プログラミング言語は、関数引数問題などの問題により、一般的に効率的な機械語コードへのコンパイルが困難です。OCamlの最適化コンパイラは、標準的なループ、レジスタ、命令の最適化に加えて、静的プログラム解析手法を用いて値のボックス化とクロージャの割り当てを最適化し、関数型プログラミング構造を多用する場合でも、結果のコードのパフォーマンスを最大限に高めます。
Xavier Leroyは「OCamlはまともなCコンパイラの少なくとも50%の性能を実現する」と述べています[8]が、直接比較は不可能です。OCaml標準ライブラリの一部の関数は、他の言語の標準ライブラリの同等の関数よりも高速なアルゴリズムで実装されています。例えば、OCaml標準ライブラリの集合の和集合の実装は、理論上、命令型言語(C++、Javaなど)の標準ライブラリの同等の関数よりも漸近的に高速です。これは、OCaml実装が集合の不変性を利用して入力集合の一部を出力で再利用できるためです(永続データ構造を参照)。

1970年代から1980年代にかけて、イギリスのコンピュータ科学者でチューリング賞受賞者のロビン・ミルナーは、エディンバラ大学のコンピュータサイエンス基礎研究所で働いていました。[9] [10]ミルナーと他の人たちは、歴史的にLispなどの言語で開発されてきた定理証明器に取り組んでいました。ミルナーは、定理証明器が証明の有効性を主張するために非証明をまとめようとする問題に繰り返し遭遇しました。 [10]その結果、彼は計算可能関数の論理のためのメタ言語を開発することになりました。この言語では、作成者は多態的な型システムを使ってのみ有効な証明を構築できます。[11] ML は、異なるマシンでの LCF の使用を簡素化するためにコンパイラに変換され、1980年代までには独自の完全なシステムになりました。[11] ML は最終的に OCaml 作成の基礎となります。
1980年代初頭、INRIAのFormelチームがML言語に興味を持つきっかけとなったいくつかの開発がありました。オックスフォード大学の研究教授であるルカ・カルデッリは、関数型抽象マシンを使用してMLのより高速な実装を開発し、ロビン・ミルナーは、さまざまな実装間の相違を避けるためのMLの新しい定義を提案しました。同時に、パリ・ディドロ大学の上級研究員であるピエール=ルイ・キュリアンは、カテゴリカルコンビネータの計算を開発し、それをラムダ計算と結び付けて、カテゴリカル抽象マシン(CAM)の定義につながりました。パリ・ディドロ大学の研究者であるガイ・クズィノーは、これがMLのコンパイル手法として応用できることを認識しました。[12]
Camlは、ジェラール・ユエ率いるINRIAのFormelチームによって最初に設計・開発されました。Camlの最初の実装は1987年に作成され、1992年まで開発が続けられました。開発はアスカンダー・スアレスが主導しましたが、1988年にスアレスが退社した後も、ピエール・ワイスとミシェル・モーニーが開発を引き継ぎました。[12]
ギ・クズィノーは、プログラミング言語の実装に関する自身の経験が当初は非常に限られており、自身の責任である不備がいくつもあったと回想している。それにもかかわらず、彼は「アスカンダー、ピエール、そしてミシェルは実に素晴らしい仕事をしてくれた」と考えている。[12]
1990年から1991年にかけて、Xavier LeroyはC言語で書かれたバイトコードインタプリタをベースにCamlの新しい実装を設計しました。これに加えて、Damien Doligezはこの実装用にメモリ管理システム(シーケンシャルガベージコレクタとも呼ばれる)を作成しました。[11]この新しい実装はCaml Lightとして知られ、古いCaml実装に取って代わり、小型デスクトップマシンで動作しました。[12]その後数年間で、Michel Maunyの構文操作ツールなどのライブラリが登場し、教育・研究チームにおけるCamlの利用を促進しました。[11]
1995年、Xavier LeroyはCamlの改良版であるCaml Special Lightをリリースした。[12]バイトコードコンパイラに最適化ネイティブコードコンパイラが追加され、パフォーマンスが大幅に向上し、 C++などの主流言語と同等のレベルになった。[11] [12]また、LeroyはStandard MLのモジュールシステムに触発された高水準モジュールシステムを設計し、抽象化とパラメータ化のための強力な機能を提供し、大規模なプログラムの構築を容易にした。[11]
ディディエ・レミーとジェローム・ヴイヨンは、オブジェクトとクラスのための表現力豊かな型システムを設計し、Caml Special Lightに統合しました。これがObjective Caml言語の誕生につながりました。この言語は1996年に初めてリリースされ、2011年にOCamlに改名されました。このオブジェクトシステムは、C++やJavaなどの言語では不健全性を引き起こしたり、実行時チェックを必要としたりする多くの一般的なオブジェクト指向イディオムを静的型安全にサポートしていました。2000年、ジャック・ガリグはObjective Camlを拡張し、多態的メソッド、バリアント、ラベル付き引数とオプション引数など、複数の新機能を追加しました。[11] [12]
過去20年間、OCamlの商用および学術的なコードベースの拡大に対応するため、言語の改良が段階的に行われてきました。[11] 2012年のOCaml 4.0リリースでは、言語の柔軟性を高めるために、一般化代数的データ型(GADT)とファーストクラスモジュールが追加されました。[11] 2022年のOCaml 5.0.0リリース[13]では、言語ランタイムが完全に書き直され、グローバルGCロックが削除され、区切られた継続によるエフェクトハンドラーが追加されました。これらの変更により、共有メモリ並列処理とカラーブラインド並行処理 がそれぞれサポートされるようになりました。
OCamlの開発は、2005年までINRIAのCristalチームによって続けられ、その後Galliumチームに引き継がれました。[14]その後、Galliumは2019年にCambiumチームに引き継がれました。[15] [16] 2023年現在、コンパイラディストリビューションのコア開発者は様々な組織から23名おり[17]、より広範なOCamlツールおよびパッケージングエコシステムの開発者は41名います。[18] 2023年、OCamlコンパイラはACM SIGPLANのプログラミング言語ソフトウェア賞を受賞しました。
OCaml は、静的 型システム、型推論、パラメトリック多態性、末尾再帰、パターン マッチング、第一級語彙閉包、関数 (パラメトリック モジュール)、例外処理、効果処理、および増分世代別自動ガベージ コレクションを特徴としています。
OCamlは、MLスタイルの型推論を汎用言語のオブジェクトシステムに拡張していることで注目に値します。これにより構造的サブタイピングが可能になり、メソッドシグネチャが互換性があれば、宣言された継承の有無にかかわらず、オブジェクト型は互換性を持つことになります(これは静的型付け言語では珍しい機能です)。
Cプリミティブへのリンクのための外部関数インターフェースが提供されており、 CとFortranの両方と互換性のある形式での効率的な数値配列の言語サポートが含まれています。OCamlは、CのメインプログラムにリンクできるOCaml関数のライブラリの作成もサポートしているため、OCamlの知識やインストール経験のないCプログラマーにもOCamlライブラリを配布できます。
OCamlには言語の不可分な部分(メタプログラミング)としてのマクロシステム、つまりプリプロセッサの組み込みサポートはありませんが、OCamlプラットフォームはそのようなプリプロセッサを作成するためのライブラリを公式にサポートしています。プリプロセッサには2種類あります。1つはC言語のようにソースコードレベルで動作するもので、もう1つは抽象構文木レベルで動作するものです。後者はPPX(Pre-Processor eXtensionの略)と呼ばれ、推奨されています。
OCaml ディストリビューションには次のものが含まれます。
ネイティブコードコンパイラは、Unix、Microsoft Windows、Apple macOSなど、多くのプラットフォームで利用可能です。移植性は、主要アーキテクチャに対する ネイティブコード生成のサポートによって実現されています。
バイトコード コンパイラは、ネイティブ コード生成が利用できない場合に、C コンパイラのみを必要とする 32 ビットまたは 64 ビットのアーキテクチャでの操作をサポートします。
OCamlバイトコードおよびネイティブコードプログラムは、プリエンプティブコンテキストスイッチを備えたマルチスレッドスタイルで記述できます。同一ドメイン[20]内のOCamlスレッドは、タイムシェアリングによってのみ実行されます。ただし、OCamlプログラムは複数のドメインを含むことができます。
OCamlコードのスニペットは、トップレベルのREPLに入力することで最も簡単に学習できます。これは、結果または定義された式の推論された型を出力する対話型のOCamlセッションです。[21] OCamlのトップレベルは、OCamlプログラムを実行するだけで起動します。
$ ocaml
OCaml バージョン 5.4.0 ヘルプ
を表示するには #help;; と入力してください。
#
その後、「#」プロンプトにコードを入力できます。例えば、1+2*3を計算するには次のようにします。
# 1 + 2 * 3 ;; - : int = 7
OCaml は式の型を「int」(機械精度の 整数)と推論し、結果「7」を返します。
次のプログラム「hello.ml」:
print_endline 「こんにちは世界!」
直接実行できます:
$ ocaml hello.ml
バイトコード実行ファイルにコンパイルされます:
$ ocamlc hello.ml -o hello
または最適化されたネイティブコード実行ファイルにコンパイルされます。
$ ocamlopt hello.ml -o hello
そして実行されました:
$ ./hello
こんにちは世界!
$
ocamlcの最初の引数「hello.ml」はコンパイルするソースファイルを指定し、「-o hello」フラグは出力ファイルを指定します。[22]
optionOCamlの型コンストラクタは、Haskellの型に似ており
、与えMaybeられたデータ型を拡張して、与えられたデータ型の値を返すか、を返すかのいずれかを行います。[23]これは、値が存在するかどうかを表すために使用されます。SomeNone
# Some 42 ;;
- : int option = Some 42
# None ;;
- : ' a option = None
これは、オプション内に int がある場合はそれを抽出して文字列に変換し、ない場合は空の文字列を返す関数の例です。
let extract o =
match o with
| Some i -> string_of_int i
| None -> "" ;;
# extract ( Some 42 );;
- : string = "42"
# extract None ;;
- : string = ""
リストはOCamlの基本的なデータ型の一つです。以下のコード例は、整数のリストであるべき1つの引数integersを受け取る再帰関数sumを定義しています。この関数が再帰的であることを示すキーワードに注目してください。この関数は、与えられた整数のリストを再帰的に反復処理し、要素の合計を返します。match文はC言語のswitch要素に似ていますが、はるかに汎用的です。
rec
let rec sum integers = (* キーワード rec は「再帰的」を意味します。 *)
整数と一致 | [] -> 0 (* integers が空の リスト [] の場合は 0 を返します。 *) | first :: rest -> first + sum rest ;; (* integers が空でないリストの場合は再帰呼び出しです。first は リストの 最初の要素で、rest は 残りの要素のリスト ( [] の場合もあります) です。 *)
# 合計 [ 1 ; 2 ; 3 ; 4 ; 5 ];;
- : int = 15
別の方法は、リストで機能する 標準のfold 関数を使用することです。
let sum integers =
List . fold_left ( fun accumulator x -> accumulator + x ) 0 integers ;;
# 合計 [ 1 ; 2 ; 3 ; 4 ; 5 ];;
- : int = 15
無名関数は単に + 演算子を適用したものな ので、次のように短縮できます。
整数の合計を リスト.fold_left (+) 0整数とします。
さらに、部分適用を利用することでリスト引数を省略することもできます。
sum =
List . fold_left (+) 0とします。
OCamlは再帰アルゴリズムを簡潔に表現するのに適しています。次のコード例は、リストを昇順にソートする クイックソートに似たアルゴリズムを実装しています。
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)。
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 )
;;
誕生日パラドックス 1 . 0 1
以下のコードは、自然数のチャーチ符号化を定義し、後続数 (succ) と加算 (add) を使用します。チャーチ数値は、関数と値を受け取り、正確に回の回数だけ適用する高階関数です。チャーチ数値を関数値から文字列に変換するには、入力の先頭に文字列を追加する関数と定数文字列を渡します。この例では、5 のチャーチ数値を「SSSSS0」として出力します。
nfxfxn"S""0"
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 モジュールに置き換えられています) は任意精度の演算機能を提供し、次のように実行中のトップレベルにロードできます。
# # "topfind"を使用します ;; # # "num"を必要とします;; # 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 . begin ` triangles ;
List . iter GlDraw . vertex2 [- 1 ., - 1 .; 0 ., 1 .; 1 ., - 1 .];
GlDraw . ends () ;
Glut . swapBuffers () in
GlMat . mode ` modelview ;
Glut.displayFunc ~ cb : render ; Glut.idleFunc ~ cb :( Some Glut.postRedisplay ) ; Glut.mainLoop ( )
OpenGLへのLablGLバインディングが必要です。プログラムは次のコマンドでバイトコードにコンパイルできます。
$ ocamlc -I +lablGL lablglut.cma lablgl.cma simple.ml -o simple
または、次のようにしてネイティブコードに変換します。
$ ocamlopt -I +lablGL lablglut.cmxa lablgl.cmxa simple.ml -o simple
または、もっと簡単には、ocamlfindビルドコマンドを使用する。
$ ocamlfind opt simple.ml -package lablgl.glut -linkpkg -o simple
そして実行します:
$ ./シンプル
OCamlでは、より高度で高性能な2Dおよび3Dグラフィカルプログラムを開発できます。OpenGLとOCamlを使用することで、作成されたプログラムはクロスプラットフォーム対応となり、多くの主要プラットフォームで変更を加えることなくコンパイルできます。
次のコードは、入力された数値nのフィボナッチ数列を計算します。末尾再帰とパターンマッチングを使用しています。
fib n =
と します。rec fib_aux m a b =
mを| 0 -> a | _ -> fib_aux ( m - 1 ) b ( a + b )と一致させます 。fib_aux n 0 1
関数は関数を入力として受け取り、関数を結果として返すことができます。例えば、関数fに2回適用すると、引数に f を2回適用する関数が生成されます。
let two ( f : ' a -> ' a ) = fun ( x : ' a ) -> f ( f x );;
let inc ( x : int ) : int = x + 1 ;;
let add2 = two inc ;;
let inc_str ( x : string ) : string = x ^ " " ^ x ;;
let add_str = two ( inc_str );;
# add2 98 ;;
- : int = 100
# add_str "Test" ;; - : string = "Test Test Test Test"
関数twiceは型変数'aを使用して、int->int関数だけでなく、型'aから自身へのマッピングを持つ任意の関数fに適用できることを示しています。特に、twiceは自身にも適用できます。
# let fourtimes f = ( twicetwice ) f ;;
val fourtimes : ( ' a - > ' a ) -> ' a -> ' a = < fun > # let add4 = fourtimes inc ;; val add4 : int -> int = < fun > # add4 98 ;; - : int = 102
MetaOCaml [24]は、 OCamlのマルチステージプログラミング拡張であり、実行時に新しいマシンコードの増分コンパイルを可能にします。状況によっては、マルチステージプログラミングを使用することで大幅な速度向上が可能です。これは、処理するデータに関するより詳細な情報が通常のコンパイル時よりも実行時に得られるため、増分コンパイラは多くの条件チェックなどの処理を最適化できるためです。
たとえば、コンパイル時に何らかのべき乗関数が 頻繁に必要になることがわかっているが、 の値は実行時にのみわかっている場合、MetaOCaml では 2 段階のべき乗関数を使用できます。
x -> x^nn
rec power n x =
とします。n = 0の場合は . < 1 >、そうでない場合はn が偶数の場合はsqr ( power ( n / 2 ) x ) 、そうでない場合は.<.~ x *. .~( power ( n - 1 ) x )>。
実行時に がわかるとすぐにn、特殊で非常に高速な累乗関数を作成できます。
.< fun x -> .~( 5 のべき乗 .< x >.)>.
結果は次のとおりです。
関数 x_1 - > ( x_1 *
y_3 = y_2 = ( x_1 * 1 ) in ( y_2 * y_2 ) in ( y_3 * y_3 ) )
新しい関数は自動的にコンパイルされます。
genfft少なくとも数十の企業がOCamlを何らかの形で使用しています。[30]注目すべき例としては以下のようなものがあります。
学術的な教育と研究の分野において、OCamlは大学や短期大学におけるコンピュータサイエンスの教育プログラムにおいて顕著な存在感を示しています。教育リソースとこれらの教育プログラムのリストはocaml.orgでご覧いただけます。
{{cite web}}:欠落または空|title=(ヘルプ)