OCaml

OCaml（ ），是一個函式式、指令式、模組化、面向物件的通用的程式語言。在和，於 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 年基於的（CAM） LFP '86: Proceedings of the 1986 ACM conference on LISP and functional programming, Pages 266–278.，重新實現了早先的 ML 方言，並用 “範疇抽象機語言” 的首字母簡寫將其命名為 Caml Research Report RT-0121, INRIA. pp.491.，Caml Light 放棄了這個抽象機器又進行了重新實現。OCaml 是開放原始碼專案，此專案的管理和大部分維護工作，已經交由法國國家資訊與自動化研究所（INRIA）。在 2000 年代早期，來自 OCaml 的元素被很多語言接納，特別是 F# 和 Scala。

哲學

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

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

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

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

曾經宣稱：“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）、、RISC-V、ARM 和 ARM64。

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

程式碼例子

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

$ ocaml

OCaml version 5.2.0

Enter #help;; for help.

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

1 + 2 * 3;;

: int = 7

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

3.1 Hello World

下面的程式 hello.ml:

print_endline "Hello World!"

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

$ ocamlc hello.ml -o hello

或者被編譯成最佳化的原生代碼可執行檔案：

$ ocamlopt hello.ml -o hello

接著執行它：

$ ./hello

Hello World!

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

3.2 階乘函式

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

let rec fact n =

if n=0 then 1 else n * fact (n - 1);;

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

let rec fact = function

0 -> 1

n -> n * fact (n - 1);;

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

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

fact 12;;

: int = 479001600

3.3 斐波那契序列

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

let fib n =

let rec fib_aux m a b =

match m with

0 -> a

 -> fibaux (m - 1) b (a + b)

in fib_aux n 0 1;;

3.4 生日問題

下列程式計算在一個屋子裡面有完全唯一的生日機率小於 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 = (yearsize -. float people) /. yearsize *. prob in

if prob

birthday_paradox 1.0 1;;

answer = 23

: unit =

3.5 合計整數列表

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

let rec sum integers = ( 關鍵字 rec 含義為遞迴。 )

match integers with

-> 0 ( 產生 0，如果 integers 為空列表 。 )

first :: rest -> first + sum rest;; (* 遞迴呼叫，如果 integers 是非空列表；

first 是這個列表的第一個元素，

而 rest 是餘下元素的列表，可能是。 *)

sum 1;2;3;4;5;;

: int = 15

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

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

3.6 快速排序

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

let rec qsort = function

->

pivot :: rest ->

let is_less x = x

3.7 高階函式

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

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 addstr = twice (incstr);;

add2 98;;

: int = 100

add_str "Test";;

: string = "Test Test Test Test"

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

let fourtimes f = (twice twice) f;;

val fourtimes : ('a -> 'a) -> 'a -> 'a =

let add4 = fourtimes inc;;

val add4 : int -> int =

add4 98;;

: int = 102

3.8 邱奇數

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

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";;

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

let = tostring (add (succ two) two);;

: string = "SSSSS0"

3.9 物件例子

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

let x =

object

val mutable x = 5

method get_x = x

method set_x y = x unit > =

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

class simple_cls =

object (self)

val mutable x = 5

method get_x = x

method set_x y = x

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

let y =

object

method get_x = 2

method set_x y = Printf.printf "% d\n" y

end;;

val y : unit > =

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

x = y;;

: bool = false

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

let setto10 a = a#set_x 10;;

val setto10 : 'a; .. > -> 'a =

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

setto10 x;;

: unit =

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

let z =

object

method blahblah = 2.5

method set_x y = Printf.printf "% d\n" y

end;;

val z : unit > =

setto10 函式對它也有效：

setto10 z;;

10

: unit =

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

type simpler_obj = ;;

type simpler_obj =

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

(x :> simpler_obj);;

: simpler_obj =

(x :> simplerobj)#getx;;

: int = 10

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

(z :> simpler_obj);;

Error: Type unit > is not a subtype of

simpler_obj =

The first object type has no method get_x

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

3.10 任意精度階乘函式

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

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

use "topfind";;
require "num";;

open Num;;

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

let rec fact n =

if n =/ Int 0 then Int 1 else n */fact (n -/ Int 1);;

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

stringofnum (fact (Int 120));;

: string =

"6689502913449127057588118054090372586752746333138029810295671352301633557244962989366874165271984981308157637893214090552534408589408121859898481114389650005964960521256960000000000000000000000000000"

3.11 繪製圖形例子

下列程式 simple.ml 使用 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

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

$ 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，結果的程式可以跨平臺編譯而在主要平臺上無需改動。

用 OCaml 寫成的程式

Coq，一個形式證明輔助器。
，一個形式證明輔助器。
，用於 C 程式原始碼的實用工具。
，分析 C 程式的一個框架。
Flow，Facebook 建立的用於推論和檢查 JavaScript 的靜態型別的一個靜態分析器。
Infer，Facebook 建立的用於 Java、C、C++ 和 Objective-C 的靜態分析器，用來檢測 iOS 和 Android 應用中的缺陷。
pyre-check，Facebook 建立的用於 Python 的型別檢查器。
Hack 程式語言編譯器，Facebook 建立，擴充套件 PHP 具有靜態型別。
Haxe 程式語言編譯器。
WebAssembly 參考直譯器，它是意圖在 web 瀏覽器內執行的低層位元組碼。
，C 的 FFT 庫，它的多數效能關鍵程式碼是由用 OCaml 寫的一個程式生成的。
，用於科學和工程計算的專用系統。
MirageOS，用純 OCaml 寫的一個單一核心程式設計框架。
，一個 OCaml 的 web 應用框架。
，用於 Web 開發的自由和開源的一個程式語言。
，一個多平臺包管理器。
MLDonkey，一個多網路 P2P 程式。
，一個檔案同步器。

有關書籍