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は、学術界発祥の他の言語と比べて、パフォーマンスを重視している点が特に際立っていると言えるでしょう。静的型システムを採用しているため、実行時の型不一致を防ぎ、動的型付け言語のパフォーマンスを低下させる実行時の型チェックや安全性チェックを不要にしています。配列の境界チェックを無効にした場合や、シリアライゼーションなどの型安全でない機能を使用した場合を除き、実行時の安全性は保証されます。ただし、これらのケースは稀であるため、実際には回避することは十分に可能です。
型チェックのオーバーヘッドを除けば、関数型プログラミング言語は、一般的に、funarg問題などの問題により、効率的な機械語コードにコンパイルするのが困難です。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言語に興味を持つきっかけとなったいくつかの進展があった。オックスフォード大学の研究教授であるLuca Cardelliは、自身の関数型抽象マシンを使用してMLのより高速な実装を開発し、Robin Milnerはさまざまな実装間の乖離を避けるためにMLの新しい定義を提案した。同時に、パリ・ディドロ大学の上級研究員であるPierre-Louis Curienは、カテゴリカルコンビネータの計算を開発し、それをラムダ計算に結び付け、カテゴリカル抽象マシン(CAM)の定義につながった。パリ・ディドロ大学の研究者であるGuy Cousineauは、これがMLのコンパイル方法として適用できることを認識した。[ 12 ]
Camlは、ジェラール・ユエ率いるINRIAのフォーミュラチームによって最初に設計・開発されました。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 ]
OCaml の商用および学術的なコードベースの増加をサポートするために、過去 20 年間にわたって言語の改良が段階的に追加されてきました。[ 11 ] 2012 年の OCaml 4.0 リリースでは、言語の柔軟性を高めるために、汎用代数データ型 (GADT) と第一級モジュールが追加されました。[ 11 ] 2022 年の OCaml 5.0.0 リリース[ 13 ]は、言語ランタイムの完全な書き直しであり、グローバル GC ロックが削除され、区切り継続によるエフェクト ハンドラが追加されました。これらの変更により、それぞれ共有メモリ並列処理と色盲並行処理のサポートが可能になります。
OCaml の開発は、INRIA の Cristal チーム内で 2005 年まで続けられ、その後 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 = 7OCaml は式の型を「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これは、オプションの中に整数値があればそれを抽出し、文字列に変換する関数、そうでなければ空の文字列を返す関数の例です。
let extract o = match o with | Some i -> string_of_int i | None -> "" ;;#抽出( Some 42 );; - : string = "42" #抽出None ;; - : string = ""リストはOCamlの基本的なデータ型の1つです。次のコード例では、整数のリストであるintethersという引数を1つ受け取る再帰関数sumを定義しています。関数が再帰的であることを示すキーワードに注目してください。この関数は、指定された整数のリストを再帰的に反復処理し、要素の合計を返します。match文はC言語のswitch文に似ていますが、はるかに汎用的です。rec
let rec sum integers = (* キーワード rec は「再帰的」を意味します。 *) match integers with | [] -> 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匿名関数は単に+演算子を適用するだけなので、以下のように短縮できます。
let sum integers = List.fold_left ( + ) 0 integersさらに、部分適用を利用することでリスト引数を省略することもできます。
let sum = List.fold_left ( + ) 0OCamlは、再帰アルゴリズムを簡潔に表現するのに適しています。以下のコード例は、クイックソートに似たアルゴリズムを実装したもので、リストを昇順にソートします。
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 ) ;;誕生日パラドックス1.01以下のコードは、後継演算子 (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 . begins ` 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 ()LablGLのOpenGLバインディングが必要です。その後、以下のコマンドでプログラムをバイトコードにコンパイルできます。
$ 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
そして実行します:
$ ./simple
OCamlを使えば、はるかに高度で高性能な2Dおよび3Dグラフィックプログラムを開発できます。OpenGLとOCamlを組み合わせることで、開発されたプログラムはクロスプラットフォームに対応し、多くの主要プラットフォームで変更なしにコンパイルできます。
以下のコードは、入力された数値nのフィボナッチ数列を計算します。末尾再帰とパターンマッチングを使用しています。
let fib n = let rec fib_aux m a b = match m with | 0 -> a | _ -> fib_aux ( m - 1 ) b ( a + b ) in fib_aux n 0 1関数は、入力として関数を受け取り、結果として関数を返すことができます。たとえば、関数fに2回適用すると、引数にfを2回適用する関数が生成されます。
let twice ( f : ' a -> ' a ) = fun ( x : ' a ) -> f ( f x );; let inc ( x : int ) : int = x + 1 ;; let add2 = twice inc ;; let inc_str ( x : string ) : string = x ^ " " ^ x ;; let add_str = twice ( inc_str );;# add2 98 ;; - : int = 100 # add_str "Test" ;; - : string = "Test Test Test Test"関数twiceは型変数'aを使用することで、int からintへの関数だけでなく、型'aから自身へのマッピングを行う任意の関数fに適用できることを示しています。特に、twice は自身にも適用できます。
# let fourtimes f = ( twice twice ) f ;; val fourtimes : ( ' a -> ' a ) -> ' a -> ' a = < fun > # let add4 = fourtimes inc ;; val add4 : int -> int = < fun > # add4 98 ;; - : int = 102MetaOCaml [ 24 ]は、実行時に新しいマシン コードをインクリメンタル コンパイルできる OCaml のマルチステージ プログラミング拡張です。マルチステージ プログラミングを使用すると、状況によっては大幅な高速化が可能になります。これは、通常のコンパイル時よりも実行時に処理するデータに関するより詳細な情報が利用できるため、インクリメンタル コンパイラが条件チェックなどの多くのケースを最適化して削除できるためです。
例えば、コンパイル時にあるべき乗関数 が頻繁に必要であることがわかっているが、その値は実行時になって初めてわかる場合、MetaOCamlでは2段階のべき乗関数を使用できます。x->x^nn
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 ))新しい関数は自動的にコンパイルされます。
genfft少なくとも数十社が何らかの形でOCamlを使用している。[ 30 ]注目すべき例としては、以下が挙げられる。
学術的な教育と研究の分野において、OCamlは大学やカレッジのコンピュータサイエンス教育プログラムで目覚ましい存在感を示しています。教育リソースとこれらの教育プログラムの一覧は、ocaml.orgで確認できます。