OCaml

**OCaml**（/oʊˈkæməl/ _oh-KAM-əl_），是一個函數式、指令式、模組化、物件導向的通用的程式語言。在 Xavier Leroy 和 Damien Doligez，於 1990 年和 1991 年實現的 ML 方言 Caml Light 之上，Didier Rémy 和 Jérôme Vouillon，於 1996 年增加了物件導向特徵，從而形成了「Objective Caml」，在 2011 年時重新命名為「OCaml」。

OCaml 工具鏈包括互動式頂層直譯器、位元組碼編譯器、最佳化的本機程式碼編譯器，可逆除錯器和一個包管理器（OPAM）。OCaml 最初開發於自動定理證明的場景中，並在靜態分析和形式方法軟體中有超凡的存在感。此外，它在系統編程、網頁編程和金融工程及其他應用領域都有嚴肅的應用。

歷史上，Ascánder Suárez 於 1987 年基於 Guy Cousineau 的範疇抽象機器（CAM），重新實現了 Gérard Huet 早先的 ML 方言，並用「範疇抽象機語言」的首字母簡寫將其命名為 Caml，Caml Light 放棄了這個抽象機器又進行了重新實現。OCaml 是開放原始碼專案，此專案的管理和大部分維護工作，已經交由法國國家資訊與自動化研究所（INRIA）。在 2000 年代早期，來自 OCaml 的元素被很多語言接納，特別是 F# 和 Scala。

## 哲學

ML 衍生語言最著稱的是靜態型別系統和類型推論編譯器。OCaml 將函數式、指令式和物件導向程式設計統一於類 ML 的型別系統之下。因此編程者不需要為了使用 OCaml 而非常熟悉純函式語言程式設計範式。

通過要求編程者在靜態型別系統的約束下工作，OCaml 消除了關聯於動態型別語言的很多有關於類型的執行時間問題。還有，OCaml 的類型推論編譯器，極大的減少了在多數靜態型別語言中對手工類型標註的需要。例如，變數的資料類型和函式的簽章，通常不需要像 Java 和 C# 語言中那樣顯式的聲明，因為它們可以從應用於這個變數和程式碼中其他值的算符和其他函式推論出來。有效的使用 OCaml 的型別系統可能要求一個編程者面對一些複雜性，但是這種規矩能得到可靠的、高效能軟體作為回報。

OCaml 與源於學術界的其他語言的最顯著區別可能是強調了效能。它的靜態型別系統防止了執行時間類型不匹配，從而排除了動態型別語言執行時間類型和安全檢查的效能負擔，卻在除了關閉陣列邊界檢查，和使用一些類型不安全特徵比如序列化之外的情況下，仍能保證執行時間安全性。這些執行時間檢查需求足夠罕見，在實踐中完全可以避免。

在型別檢查開銷之外，函式語言程式設計語言，要編譯成高效的機器語言程式碼，由於如函式參數問題這樣的要點，一般而言是具有挑戰性的。與標準的迴圈、暫存器和指令最佳化一起，OCaml 的最佳化編譯器採用靜態程式分析方法，來最佳化值包裝（boxing）和閉包分配，幫助結果程式碼得到最大化的效能，即使它大量使用了函式語言程式設計構造。

Xavier Leroy 曾經宣稱：「OCaml 至少提供了像樣的 C 編譯器的 50% 的效能」，儘管直接比較是不可能的。在 OCaml 標準庫中的一些函式，是採用比在其他語言標準庫中等價的函式更快的演算法實現的。例如，在 OCaml 標準庫中集合併集的實現，在理論上比指令式語言（例如 C++、Java）的標準庫中的等價函式，要漸進性的更快，因為 OCaml 實現利用了集合的不可變性，而在輸出中重用了輸入集合的一些部份（參見可持久化資料結構）。

## 特徵

OCaml 的特徵包括：靜態型別系統、類型推論、參數多型、尾遞迴、模式匹配、頭等詞法閉包、函子（參數化模組）、例外處理和增量分代自動垃圾回收。

OCaml 著稱於將 ML 風格類型推論，擴充到通用語言中的物件系統。這允許了結構子類型，這裡的物件類型是相容的，如果它們的方法簽章是相容的，不用管它們聲明的繼承，這是在靜態型別語言中不尋常的特徵。

OCaml 提供了連結到 C 原語的外界函式介面，包括了相容於 C 和 Fortran 二者格式的對高效數值陣列的語言支援。OCaml 還支援建立可以連結到用 C 寫的 `main` 程式的 OCaml 函式庫。

OCaml 發行包含了：

* 詞法分析和語法解析工具分別叫做 `ocamllex` 和 `ocamlyacc`。
* 支援步進回溯的除錯器用來調查錯誤。
* 文件生成器。
* 剖析器用來測量效能。
* 很多通用函式庫。

本機程式碼編譯器可以在很多平台上獲得，包括 Unix、Microsoft Windows 和 Apple macOS。可移植性是通過至此主要架構的本機程式碼生成實現的：IA-32、X86-64（AMD64）、Power、RISC-V、ARM 和 ARM64。

OCaml 位元組碼和本機程式碼程式可以用多執行緒風格書寫，具有搶占式上下文交換。但是由於當前唯一可得的語言完全實現 INRIA OCaml 的垃圾回收器，不是為並行性而設計的，對稱多處理是不支援的。在相同行程中的 OCaml 執行緒只能分時執行。但是有一些分散式計算庫比如 Functory 和 Plasma。

## 程式碼範例

OCaml 的程式碼片段可以通過鍵入到頂層 REPL 中來很容易的研習。這是一個互動式的 OCaml 對談，它列印結果或定義的表達式的推論出的類型。OCaml 頂層可以通過簡單執行 OCaml 程式來啟動：

可以接著在 `#` 提示符處鍵入程式碼。例如計算 `1+2*3`:

OCaml 推論出這個表達式的類型是 `int`（機器精度整數）並給出結果 `7`。

### Hello World

下面的程式 `hello.ml`:

可以被編譯成位元組碼可執行檔：

或者被編譯成最佳化的本地程式碼可執行檔：

接著執行它：

給 ocamlc 的第一個實際參數 `hello.ml`，指定要編譯的原始檔而 `-o hello` 標誌指定了輸出檔案。

### 階乘函式

很多數學函式，比如階乘，可以很自然的表示為純粹的函式形式：

這個函式可以使用模式匹配等價的寫為：

後者形式是階乘作為遞推關係的數學定義。

編譯器將這個函式的類型推論為 `int -> int`，意味著這個函式將 `int` 對映到 `int`。例如，`12!`：

### 斐波那契序列

下列程式碼計算輸入數 `n` 的斐波那契數列。它使用了尾遞迴和模式匹配。

### 生日問題

下列程式計算在一個屋子裡面有完全唯一的生日概率小於 50% 的最少人數，在生日問題中，對於 1 個人這個概率是 365/365（或 100%），對於 2 個人是 364/365，對於 3 個人是 364/365 × 363/365，最終答案是 23 個人：

### 合計整數列表

列表是 OCaml 中的基礎資料類型之一。下面的程式碼範例定義遞迴函式 `sum`，它接受一個實際參數 `integers`，而它被假定為整數的列表。注意關鍵字 `rec` 指示了這個函式是遞迴的。這個函式遞迴的在給定整數列表之上進行迭代，並提供這些元素的一個總和。`match` 語句類似於 C 的 `switch` 語句，但要更加一般性。

另一種方式是對列表使用標準的 fold 高階函式:

因為匿名函式是簡單的 `+` 算符應用，它可以簡寫為：

進一步的，還可以通過採用部份應用省略列表實際參數：

### 快速排序

OCaml 自身可提供對遞迴演算法的簡介表達。下列程式碼範例實現了類似於以升序排序一個列表的 quicksort 的一個演算法：

### 高階函式

函式可以接受函式作為參數並且返回函式作為結果。例如，應用 `twice` 到函式 `f` 產生應用 `f` 到它的實際參數兩次的一個函式：

函式 `twice` 使用類型變數 `'a`，來指示它可以應用於對映一個類型 `'a` 到自身的任何 `f`，而非只應用於 `int->int` 函式。特別是，`twice` 甚至可以應用於自身：

### 邱奇數

下列程式碼定義自然數的邱奇編碼，具有後繼（succ）和加法（add）。邱奇數 `n` 是接受一個函式 `f` 和一個值 `x` 的一個高階函式，它應用 `f` 到 `x` 精確的 `n` 次：

為了將一個邱奇數從函式值轉換成一個字串，這裡把它傳遞給向其輸入和常數字串 `"0"` 前置上字串 `"S"` 的函式 `to_string`：

### 物件範例

在 OCaml 中物件，通過它們的方法的名字和類型，按結構來確定類型。物件可以直接建立（立即物件），而不用通過有名稱的類。例如：

這裡 OCaml 互動式執行時間系統列印出這個物件的推論類型。它的類型 `< get_x : int; set_x : int -> unit >`，只由它的方法來定義。換句話說，`x` 的類型由方法類型 `get_x : int` 和 `set_x : int -> unit` 而非任何名字來定義。類只充當建立物件的函式，例如上例中的物件可以用類來定義，並接著用 `new` 算符來建立：

要定義有相同的方法和方法類型的另一個物件：

OCaml 將它們視為有相同的類型。例如，等式算符被確定類型為只接受有相同類型的兩個值：

所有儘管它們有不同的值，卻必定是相同類型的，否則連型別檢查都不會做完。這展示了類型等價是結構性的。可以定義呼叫一個方法的函式：

第一個實際參數的推論類型 `< set_x : int -> 'a; .. >` 是值得關注的。`..` 意味著第一個實際參數，可以是有接受一個 `int` 作為實際參數的 `set_x` 方法的任何物件。所以它可以用在物件 `x` 之上：

另一個物件可以碰巧有這個方法和方法類型；其他方法是無關緊要的：

`set_to_10` 函式對它也有效：

這展示了對於事物比如方法呼叫的相容性是由結構來確定的。下面為只有一個 `get_x` 方法而沒有其他方法的物件定義一個類型同義詞（synonym）：

物件 `x` 不是這個類型的；但在結構上 `x` 是這個類型的一個子類型，因為 `x` 包含它的方法的一個超集。所以 `x` 可以強制（coerce）成這個類型：

但是物件 `z` 不行，因為它不是一個結構子類型：

這展示了拓寬強制的相容性是結構性的。

### 任意精度階乘函式

可以從 OCaml 存取各種各樣的庫。比如，OCaml 有內建的任意精度算術。由於階乘函式增長得非常迅速，會很快溢位機器精度的整數。因此階乘很適合選用任意精度算術。

在 OCaml 中，`Num` 模組（現在被 `ZArith` 所取代）提供了任意精度算術，比如在 Ubuntu 中安裝它：`sudo apt install libnum-ocaml-dev`，它可以如下這樣裝載到執行中的頂層中：

階乘函式可以使用任意精度算符 `=/`、`*/` 和 `-/` 寫為：

這個函式可以計算非常大的階乘比如 `120!`：

### 繪製圖形範例

下列程式 `simple.ml` 使用 OpenGL 呈現一個緩慢旋轉的 2D 三角形:

需要事先安裝負責繫結到 OpenGL 的 LablGL，比如在 Ubuntu 中安裝它：`sudo apt install liblablgl-ocaml-dev`，這個程式可以如下這樣編譯成位元組碼：

或編譯成本機程式碼：

或者簡單的使用 `ocamlfind` 建造命令：

然後執行：

可以使用 OCaml 開發非常複雜、高效能的 2D 和 3D 圖形程式。使用 OpenGL 和 OCaml，結果的程式可以跨平台編譯而在主要平台上無需改動。

## 用 OCaml 寫成的程式

## 參見

* Caml，OCaml 的先驅語言。
* F#，由 MSR 開發，是一個基於 OCaml 的一個以.NET 為目標的程式語言。
* F\*，由 MSR 和 INRIA 主導開發，是一個基於 OCaml 的依值型別函數式程式語言。
* Reason，OCaml 的語法擴充和工具鏈，也可以轉譯成 JavaScript。

## 有關書籍

* Yaron Minsky, Anil Madhavapeddy. Real World OCaml, Functional programming for the masses, 2nd Edition. 2021 [2021-09-10]. （原始內容存檔於 2022-05-14）.
* Emmanuel Chailloux, Pascal Manoury, Bruno Pagano. Developing Applications With Objective Caml (PDF). 2000 [2021-09-10]. （原始內容 (PDF) 存檔於 2022-02-23）.
* Jason Hickey. Introduction to the Objective Caml Programming Language. 2002 [2021-09-10]. （原始內容存檔於 2021-09-10）.

## 參照

## 外部連結

* 官方網站
* OCaml manual （頁面存檔備份，存於網際網路檔案館）
* OCaml Package Manager （頁面存檔備份，存於網際網路檔案館）
* Awesome OCaml （頁面存檔備份，存於網際網路檔案館）