OCaml

Caml程式語言的擴充

OCaml/ˈkæməl/ oh-KAM-əl),是一個函數式指令式模組化[1]物件導向通用程式語言。在Xavier Leroy英語Xavier LeroyDamien Doligez英語Damien Doligez,於1990年和1991年實現的ML方言Caml Light之上[5],Didier Rémy和Jérôme Vouillon,於1996年增加了物件導向特徵[2],從而形成了「Objective Caml」[6],在2011年時重新命名為「OCaml」[7]

OCaml
編程範型多範式函數式指令式模組化[1]物件導向[2]
語言家族ML
設計者Xavier Leroy英語Xavier Leroy, Damien Doligez英語Damien Doligez, Didier Rémy, Jérôme Vouillon
實作者INRIA
面市時間1996年,​28年前​(1996
目前版本
  • 5.2.0(2024年5月13日;穩定版本)[3]
編輯維基數據鏈結
型態系統靜態類型推論
作業系統跨平台
授權條款GNU較寬鬆公共許可證
網站ocaml.org 編輯維基數據鏈結
衍生副語言
F#, JoCaml英語JoCaml, MetaOCaml[4]
啟發語言
C, Caml, Modula-2[1], Standard ML
影響語言
ATS英語ATS (programming language), Coq, Elm, F#, F*, Haxe, Opa英語Opa (programming language), Rust, Scala

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

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

哲學

編輯

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

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

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

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

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

特徵

編輯

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

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

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

OCaml發行包含了:

本機程式碼編譯器可以在很多平台上獲得,包括UnixMicrosoft WindowsApple macOS。可移植性是通過至此主要架構的本機程式碼生成英語code generation (compiler)實現的:IA-32X86-64(AMD64)、Power英語Power ISARISC-VARMARM64[13]

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

程式碼範例

編輯

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

$ ocaml
        OCaml version 4.13.1

#

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

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

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

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標誌指定了輸出檔案[18]

階乘函式

編輯

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

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

斐波那契序列

編輯

下列程式碼計算輸入數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;;

生日問題

編輯

下列程式計算在一個屋子裡面有完全唯一的生日概率小於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;;
answer = 23
- : unit = ()

合計整數列表

編輯

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

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

進一步的,還可以通過採用部份應用英語Partial application省略列表實際參數:

let sum =
  List.fold_left (+) 0

快速排序

編輯

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

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

高階函式

編輯

函式可以接受函式作為參數並且返回函式作為結果。例如,應用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 add_str = twice(inc_str);;
# 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 = <fun>
# let add4 = fourtimes inc;;
val add4 : int -> int = <fun>
# add4 98;;
- : int = 102

邱奇數

編輯

下列程式碼定義自然數邱奇編碼,具有後繼(succ)和加法(add)。邱奇數n是接受一個函式f和一個值x的一個高階函式,它應用fx精確的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 _ = to_string (add (succ two) two);;
- : string = "SSSSS0"

對象範例

編輯

在OCaml中對象,通過它們的方法的名字和類型,按結構來確定類型。對象可以直接建立(立即對象),而不用通過有名稱的類。例如:

# let x =
    object
      val mutable x = 5
      method get_x = x
      method set_x y = x <- y
    end;;
val x : < get_x : int; set_x : int -> unit > = <obj>

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

# class simple_cls =
    object (self)
      val mutable x = 5
      method get_x = x
      method set_x y = x <- y
    end;;
  let x = new simple_cls;;

要定義有相同的方法和方法類型的另一個對象:

# let y =
    object
      method get_x = 2
      method set_x y = Printf.printf "%d\n" y
    end;;
val y : < get_x : int; set_x : int -> unit > = <obj>

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

# x = y;;
- : bool = false

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

# let set_to_10 a = a#set_x 10;;
val set_to_10 : < set_x : int -> 'a; .. > -> 'a = <fun>

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

# set_to_10 x;;
- : unit = ()

另一個對象可以碰巧有這個方法和方法類型;其他方法是無關緊要的:

# let z =
    object
      method blahblah = 2.5
      method set_x y = Printf.printf "%d\n" y
    end;;
val z : < blahblah : float; set_x : int -> unit > = <obj>

set_to_10函式對它也有效:

# set_to_10 z;;
10
- : unit = ()

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

# type simpler_obj = < get_x : int >;;
type simpler_obj = < get_x : int >

對象x不是這個類型的;但在結構上x是這個類型的一個子類型,因為x包含它的方法的一個超集。所以x可以強制(coerce)成這個類型:

# (x :> simpler_obj);;
- : simpler_obj = <obj>
# (x :> simpler_obj)#get_x;;
- : int = 10

但是對象z不行,因為它不是一個結構子類型:

# (z :> simpler_obj);;
Error: Type < blahblah : float; set_x : int -> unit > is not a subtype of
         simpler_obj = < get_x : int > 
       The first object type has no method get_x

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

任意精度階乘函式

編輯

可以從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!

# string_of_num (fact (Int 120));;
- : string =
"6689502913449127057588118054090372586752746333138029810295671352301633557244962989366874165271984981308157637893214090552534408589408121859898481114389650005964960521256960000000000000000000000000000"

繪製圖形範例

編輯

下列程式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寫成的程式

編輯

參見

編輯

有關書籍

編輯

參照

編輯
  1. ^ 1.0 1.1 1.2 Xavier Leroy. Manifest types, modules, and separate compilation (PDF). Principles of Programming Languages. 1994 [2021-09-06]. (原始內容 (PDF)存檔於2021-10-22). 
  2. ^ 2.0 2.1 Didier Rémy. Type inference for records in a natural extension of ML. Research Report RR-1431, INRIA. 1991 [2021-09-10]. (原始內容存檔於2022-04-06). 
    Didier Rémy, Jérôme Vouillon. Objective ML: a simple object-oriented extension of ML (PDF). 1997 [2021-09-06]. (原始內容 (PDF)存檔於2022-01-21). 
    Didier Rémy, Jérôme Vouillon. Objective ML: An effective object-oriented extension to ML (PDF). 1998 [2021-09-06]. (原始內容 (PDF)存檔於2022-01-20). 
  3. ^ OCaml 5.2.0 Release Notes. [2024年5月24日]. 
  4. ^ MetaOCaml -- an OCaml dialect for multi-stage programming. [2021-08-28]. (原始內容存檔於2021-08-29). 
  5. ^ Xavier Leroy英語Xavier Leroy. The Caml Light system Release 0.74, documentation and user's guide. 1997 [2021-09-02]. (原始內容存檔於2022-03-08). 
  6. ^ Xavier Leroy. The Objective Caml system release 1.07, Documentation and user's manual. 1997 [2021-09-02]. (原始內容存檔於2022-01-23). 
  7. ^ A History of Caml. [2021-09-02]. (原始內容存檔於2022-04-13). Our main reason for developing Caml was to use it for sofware development inside Formel. Indeed, it was used for developing the Coq system ……. We were reluctant to adopt a standard that could later prevent us from adapting the language to our programming needs. ……We did incorporate into Caml most of the improvements brought by Standard ML over Edinburgh ML. ……The first implementation of Caml appeared in 1987 and was further developed until 1992. It was created mainly by Ascander Suarez. ……
    In 1990 and 1991, Xavier Leroy designed a completely new implementation of Caml, based on a bytecode interpreter written in C. Damien Doligez provided an excellent memory management system. ……In 1995, Xavier Leroy released Caml Special Light, which improved over Caml Light in several ways. In 1995, Xavier Leroy released Caml Special Light, which improved over Caml Light in several ways. First, an optimizing native-code compiler was added to the bytecode compiler. ……Second, Caml Special Light offered a high-level module system, designed by Xavier Leroy and inspired by the module system of Standard ML. ……Didier Rémy, later joined by Jérôme Vouillon, designed an elegant and highly expressive type system for objects and classes. This design was integrated and implemented within Caml Special Light, leading to the Objective Caml language and implementation, first released in 1996 and renamed to OCaml in 2011.
     
  8. ^ G. Cousineau, P.-L. Curien, M. Mauny. The categorical abstract machine. 1985 [2021-09-03]. (原始內容存檔於2021-09-03).  LNCS, 201, Functional programming languages computer architecture, pp.~50-64.
    Michel Mauny, Ascánder Suárez. Implementing functional languages in the Categorical Abstract Machine (PDF). 1986 [2021-09-07]. (原始內容 (PDF)存檔於2022-01-28).  LFP '86: Proceedings of the 1986 ACM conference on LISP and functional programming, Pages 266–278.
  9. ^ G. Cousineau, M. Gordon, G. Huet, R. Milner, L. C. Paulson, C. Wadsworth. The ML Handbook, Version 6.2. Internal document. Project Formel, INRIA. July 1985. 
    Christoph Kreitz, Vincent Rahli. Introduction to Classic ML (PDF). 2011 [2021-09-11]. (原始內容 (PDF)存檔於2022-01-29). This handbook is a revised edition of Section 2 of 『Edinburgh LCF』, by M. Gordon, R. Milner, and C. Wadsworth, published in 1979 as Springer Verlag Lecture Notes in Computer Science no 78. ……The language is somewhere in between the original ML from LCF and standard ML, since Guy Cousineau added the constructors and call by patterns. This is a LISP based implementation, compatible for Maclisp on Multics, Franzlisp on VAX under Unix, Zetalisp on Symbolics 3600, and Le Lisp on 68000, VAX, Multics, Perkin-Elmer, etc... Video interfaces have been implemented by Philippe Le Chenadec on Multics, and by Maurice Migeon on Symbolics 3600. The ML system is maintained and distributed jointly by INRIA and the University of Cambridge. 
  10. ^ Guy Cousineau, Gérard Huet英語Gérard Huet. The CAML primer Version 2.6.1. 1990 [2021-09-07]. (原始內容存檔於2022-05-04).  RT-0122, INRIA. pp.78.
    Pierre Weis, Maria Virginia Aponte, Alain Laville, Michel Mauny, Ascander Suarez. The CAML reference manual Version 2.6.1. 1990 [2021-09-07]. (原始內容存檔於2022-04-06).  [Research Report] RT-0121, INRIA. pp.491.
  11. ^ Xavier Leroy. The ZINC experiment : an economical implementation of the ML language. 1990 [2021-09-06]. (原始內容存檔於2022-04-06).  RT-0117, INRIA.
  12. ^ Linux Weekly News頁面存檔備份,存於網際網路檔案館).
  13. ^ ocaml/asmcomp at trunk · ocaml/ocaml · GitHub. GitHub. [2 May 2015]. (原始內容存檔於2022-05-07). 
  14. ^ Archives of the Caml mailing list > Message from Xavier Leroy. [2021-09-10]. (原始內容存檔於2022-03-31). 
  15. ^ Functory. [2021-08-30]. (原始內容存檔於2022-01-20). 
  16. ^ ocamlnet/Plasma. [2021-08-30]. (原始內容存檔於2022-03-23). 
  17. ^ OCaml - The toplevel system or REPL (ocaml). ocaml.org. [2021-05-17]. (原始內容存檔於2021-09-11). 
  18. ^ Batch compilation (ocamlc)頁面存檔備份,存於網際網路檔案館).
  19. ^ Object types. [2021-09-10]. (原始內容存檔於2022-03-10). 
  20. ^ Coq頁面存檔備份,存於網際網路檔案館
  21. ^ Flow: A Static Type Checker for JavaScript. [2021-08-29]. (原始內容存檔於2022-04-08). 
  22. ^ Infer static analyzer. [2021-08-29]. (原始內容存檔於2022-05-12). 
  23. ^ GitHub - facebook/pyre-check: Performant type-checking for python.. 9 February 2019 [2021-08-29]. (原始內容存檔於2022-05-05) –透過GitHub. 
  24. ^ WebAssembly/spec: WebAssembly specification, reference interpreter, and test suite.. World Wide Web Consortium. 5 December 2019 [2021-05-14]. (原始內容存檔於2022-05-12) –透過GitHub. 
  25. ^ FFTW頁面存檔備份,存於網際網路檔案館
  26. ^ MirageOS. [2021-08-29]. (原始內容存檔於2022-04-20). 
  27. ^ Unison. [2021-08-28]. (原始內容存檔於2021-07-12). 

外部連結

編輯