ML（Meta Language：元語言），是一個函式式、指令式的通用的程式語言，它著稱於使用了多型的 HindleyMilner 型別推論 Journal of Computer and System Sciences 17 3348375。ML 能自動的指定多數的型別，不要求顯式的型別標註，而且能夠確保型別安全，已經正式證明了有良好型別的 ML 程式不會導致執行時間型別錯誤。 ML 提供了對函式實際引數的模式匹配、垃圾回收、指令式程式設計、傳值呼叫和柯里化。它被大量的用於程式語言研究之中，並且是全面規定了的和使用形式語義驗證了的少數語言之一。它的型別和模式匹配使得它非常適合並且經常用於在其他形式語言上進行操作，比如在編譯器構造、自動化定理證明和形式驗證中。 歷史 在 1970 年代早期，ML 由愛丁堡大學的羅賓米爾納及他人研製出來，用於在定理證明器中開發證明策略。LCF 的語言是 PPλ，它是一階邏輯演算與有型別的多型 λ 演算的結合，以 ML 作為元語言。ML 的語法從 ISWIM 及其擴充套件實現 PAL 得到了啟發， ML 執行在 DEC10TOPS10 主機的 Stanford LISP 16 下。 在 1980 年，於愛丁堡大學的 VAXVMS 系統上開發了 ML 編譯器，它被稱為 VAX ML，以區別於 LCF 版本的 DEC10 ML。VAX ML 的編譯器和執行時間系統二者，都是用 Pascal 書寫而建立在 函式抽象機器（FAM）之上 Bell Labs Technical Report TR107 In Proceedings of the 1984 ACM Symposium on LISP and Functional Programming pages 208217 New York NY USA ACM。在 1982 年，愛丁堡大學的 Kevin Mitchell，用 VAX ML 重寫了 VAX ML 編譯器，隨即 John Scott 和加入了開發，在又進行一系列重寫改進之後，新的編譯器被稱為 Edinburgh ML。 在 1981 年，INRIA 的，將最初的 LCF ML 適配到 Multics 系統的 Maclisp 下，並且增加了編譯器。這個實現被描述於 INRIA 內部文件 ML 手冊 之中，它被開發者自稱為 Le_ML。劍橋大學的在 1985 年用它開發了基於 Franz Lisp 的 Cambridge LCF，進而劍橋大學的又用它開發了基於 Common Lisp 的。 在 1983 年，Robin Milner 由兩個動機驅使開始重新設計 ML。其一是愛丁堡大學的及其小組在規定上的工作，具體化為規定語言 CLEAR，和表達可執行規定的函式式語言 HOPE Conference Record of the 1980 LISP Conference Stanford University pp 136143。這項工作與 ML 有關的是兩方面成果：首先，HOPE 擁有優雅的程式設計特徵，特別是模式匹配，和子句式函式定義；其次，是使用在介面中的簽名，進行規定的模組化構造的想法。其二是在 VAX ML 上的工作，透過增加命名記錄和可變型別，擴充套件了 ML 中資料型別的品目。 在 1984 年，貝爾實驗室的 David MacQueen 提出了對 Standard ML 的模組系統的設計。在 Standard ML 的持續設計期間 LFCS Department of Computer Science University of Edinburgh LFCS Department of Computer Science University of Edinburgh LFCS Department of Computer Science University of Edinburgh LFCS Department of Computer Science University of Edinburgh LFCS Department of Computer Science University of Edinburgh，Edinburgh ML 被漸進的修改，充當了 Standard ML 的近似原型實現。在 1986 年，普林斯頓大學的和貝爾實驗室的 David MacQueen，以 Edinburgh Standard ML 作為起步開發環境，開始了專注於生成高質量機器程式碼的 Standard ML of New Jersey 的活躍開發。 在 1990 年，Robin Milner、和制定的 Standard ML 的正式規定《The Definition of Standard ML》最終完成；在 1997 年，這個標準規定增補了 David MacQueen 為作者並進行了修訂。在 1989 年，、Nick Rothwell 和 David N Turner 於愛丁堡大學開始開發 ML Kit 編譯器，為了強調清晰性而非高效，將標準定義直接轉譯成一組 Standard ML 模組；在 1992 年和 1993 年期間，主要透過愛丁堡大學的 Nick Rothwell 和哥本哈根大學（DIKU）的 Lars Birkedal 的工作，ML Kit 第一版完成並開源發行。 在 1987 年，INRIA 的 Ascánder Suárez，基於巴黎第七大學的的 （CAM） LNCS 201 Functional programming languages computer architecture pp5064 LFP 86 Proceedings of the 1986 ACM conference on LISP and functional programming Pages 266278，利用 Le Lisp 的執行時間系統重新實現了 Le_ML，並正式命名為 Caml RT0122 INRIA pp78 Research Report RT0121 INRIA pp491。在 1990 年和 1991 年，INRIA 的基於用 C 實現的位元組碼直譯器 RT0117 INRIA，利用提供的記憶體管理系統重新實現了 Caml，並稱其為 Caml Light。在 1995 年，Xavier Leroy 又增加了機器程式碼編譯器和高層模組系統，這個版本也稱為 Caml Special Light。在 1996 年，INRIA 的 Didier Rémy 和 Jérôme Vouillon，向 Caml Special Light 增加了面向物件特徵，從而形成了 OCaml。 今天 ML 家族的兩個主要的方言是 Standard ML 和 OCaml。ML 的實力大多被用於語言設計和操作，比如建構編譯器、分析器、定理證明器，但是它作為通用語言也被用於生物資訊和財務系統等領域。ML 確立了靜態型別函數語言程式設計範型，從而在程式語言歷史上佔有顯要地位，它的思想在影響了眾多的語言，例如 Haskell、Nemerle、和 Elm。 解釋與編譯 ML 程式碼片段很容易透過將其錄入到 頂層 來研習，它也叫作讀取求值輸出迴圈或 REPL。這是列印結果或定義的表示式的推論型別的互動式會話。很多 SML 實現提供互動式 REPL，比如 SMLNJ： sml Standard ML of New Jersey v11079 built Mon Apr 22 101455 2024 可以在提示符 後鍵入程式碼。例如計算 1 2 3 1 2 3 val it 7 int it val it 7 int 頂層推論這個表示式的型別為 int 並給出結果 7。如果輸入不完全，直譯器會列印第二提示符 ，這時通常可以用；終結輸入。it 是給未指定變數的表示式的標準變數。輸入 controlC 可返回直譯器頂層，輸入 controlD 可退出直譯器。可以使用第三方工具 rlwrap 執行 SMLNJ 直譯器，它處理使用者輸入並提供 readline 的行編輯、持久歷史和補全功能。 下面是輸出 hello world 的例子程式碼，在 SMLNJ 直譯器下執行它： print Hello worldn Hello world val it unit val _ print Hello worldn Hello world 使用 MLton 編譯器進行編譯執行它： echo print Hello worldn helloworldsml mlton helloworldsml helloworld Hello world 使用 LunarML 源到源編譯器，將前面的例子編譯成 Javascript 程式碼並使用 Nodejs 執行它 echo print Hello worldn helloworldsml lunarml compile nodejs helloworldsml nodejs helloworldmjs Hello world 核心語言 不同於純函數語言程式設計語言，ML 是兼具一些指令式特徵的函數語言程式設計語言。ML 的特徵包括：傳值呼叫的求值策略，頭等函式，帶有垃圾收集的自動記憶體管理，引數多型，靜態型別，型別推論，代數資料型別，模式匹配和例外處理。不同於 Haskell，ML 與大多數程式語言一樣使用及早求值。 用 ML 書寫的程式構成自要被求值的，而非語句或命令，儘管一些表示式返回一個平凡的 unit 值並且只為其副作用而求值。以下章節介紹採用 Standard ML 的語法和語義。 函式 就像所有的函式式語言一樣，ML 的關鍵特徵是函式，它被用於進行抽象。例如階乘函式用純 ML 可表達為： fun fac 0 1 fac n n fac n 1 這裡將階乘描述為遞迴函式，具有一個單一的終止基礎情況。它類似於在數學教科書見到的階乘描述。多數 ML 程式碼在設施和語法上類似於數學。 憑藉型別推論編譯器能推匯出，fac 接受整數 0 作為實際引數，則形式引數 n 也是整數型別 int，而 fac 0 的結果是整數 1，則函式 fac 的結果也是整數型別。函式 fac 接受一個整數的形式引數並返回一個整數結果，它作為一個整體從而有著 從整數到整數的函式 型別 int int。函式及其形式引數的 型別 還可以用型別標註（annotation）來描述，使用 E t 表示法，它可以被讀作表示式 E 有型別 t，它是可選也可忽略的。使用型別標註，這個例子可重寫為如下： fun fac 0 1 fac n int int n fac n 1 這個函式還依賴於模式匹配，這是 ML 語言的重要部份。注意函式形式引數不必須在圓括號內但要用空格分隔。當一個函式的實際引數是 0，它將返回整數 1。對於所有其他情況，嘗試第二行。這是一個遞迴，並且再次執行這個函式直到達到基礎情況。它可以使用 case 表示式重寫為： fun fac n case n of 0 1 n n fac n 1 這裡 case 介入了模式和對應表示式的序列。它還可以重寫為將識別符號繫結到 lambda 函式： val rec fac fn 0 1 n n fac n 1 這裡的關鍵字 val 介入了識別符號到值的繫結，fn 介入了匿名函式的定義，它可以用在 fun 的位置上，但使用 算符而非 。繫結到遞迴的匿名函式需要使用 rec 關鍵字來指示。 透過將主要工作寫入尾遞迴風格的內部迭代函式，藉助於語言編譯或解釋系統進行的尾呼叫最佳化，這個函式可以得以增進效能，它的呼叫棧不需要隨函式呼叫數目而成比例的增長。對這個函式可以採用向內部函式增加額外的 累加器 形式引數 acc 來實現： fun fac n let fun loop 0 acc acc loop n acc loop n 1 n acc in loop n 1 end let 表示式的值是在 in 和 end 之間表示式的值。這個遞迴函式的實現不保證能夠終止，因為負數實際引數會導致遞迴呼叫的無窮降鏈條件。更健壯的實現會在遞迴前檢查實際引數為非負數，並在有問題的情況，即 n 是負數的時候，啟用例外處理： fun fac n let fun loop 0 acc acc loop n acc loop n 1 n acc in if n 0 then raise Fail negative argument else loop n 1 end 型別 有一些基本型別可以當作是 內建 的，因為它們是在 Standard ML 標準基本庫中預先定義的，並且語言為它們提供表示法，比如 34 是整數，而 34 是字串。一些最常用的基本型別是： int 整數，比如 3 或～12。注意波浪號～表示負號。 real 浮點數，比如 42 或～64。Standard ML 不隱含的提升整數為浮點數，因此表示式 2 567 是無效的。 string 字串，比如 this is a string 或 （空串）。 char 字元，比如 y 或 n（換行符）。 bool 布林值，它是要麼 true 要麼 false。產生 bool 值的有比較算符 、、、、、，邏輯函式 not 和短路求值的中綴算符 andalso、orelse。 包括上述基本型別的各種型別可以用多種方式組合。一種方式是元組，它是值的有序集合，比如表示式 1 2 的型別是 int int，而 foo false 的型別是 string bool。可以使用 1 foo false 這樣的語法來提取元組的指定次序的成員。 有 0 元組 ，它的型別指示為 unit。但是沒有 1 元組，或者說在例子 1 和 1 之間沒有區別，都有型別 int。元組可以巢狀，1 2 3 不同於 1 2 3 和 1 2 3 二者。前者的型別是 int int int，其他兩個的型別分別是 int int int 和 int int int。 組合值的另一種方式是記錄。記錄很像元組，除了它的成員是有名字的而非有次序的，例如 a 50 b five 有型別 a real b string，這同於型別 b string a real。可以使用 a a 50 b five 這樣的語法來選取記錄的指定名字的欄位。 Standard ML 中的函式只接受一個值作為引數，而不是引數的一個列表，可以使用上述元組模式匹配來實際上傳遞多個引數。函式還可以返回一個元組。例如： fun sum a b a b fun average pair sum pair div 2 infix averaged_with fun a averaged_with b average a b val c 3 averaged_with 7 fun int_pair n int n n 這裡第一段建立了兩個型別是 int int int 的函式 sum 和 average。第二段建立中綴運算元 averaged_with，接著定義它為型別 int int int 的函式。最後的 int_pair 函式的型別是 int int int。 下列函式是多型型別的一個例子： fun pair x x x 編譯器無法推論出的 pair 的特殊化了的型別，它可以是 int int int、real real real 甚至是 int real string int real string int real string。在 Standard ML 中，它可以簡單指定為多型型別 a a a，這裡的 a（讀作 alpha）是一個型別變數，指示任何可能的型別。在上述定義下，pair 3 和 pair x 都是有良好定義的，分別產生 3 3 和 x x。 SML 標準基本庫包括了過載識別符號：、、、div、mod、、、abs、、、、。標準基本庫提供了多型函式：、、o、before、ignore。中綴運算子可以有從預設最低 0 到最高 9 的任何運算子優先順序。標準基本庫提供瞭如下內建中綴規定： infix 7 div mod infix 6 infixr 5 infix 4 infix 3 o infix 0 before 等式型別 算符 和 分別被定義為多型的等式和不等式。 它確定兩個值是否相等，有著型別 a a bool。這意味著它的兩個運算數必須有相同的型別，這個型別必須是等式型別（eqtype）。上述基本型別中除了 real 之外，int、real、string、char 和 bool 都是等式型別。 例如：3 3、3 3、3 3 和 true true，都是有效的表示式並求值為 true；而 3 4 是有效表示式並求值為 false，30 30 是無效表示式而被編譯器拒絕。這是因為 IEEE 浮點數等式打破了 ML 中對等式的一些要求。特別是 nan 不等於自身，所以關係不是自反的。 元組和記錄型別是等式型別，當時且僅當它的每個成員型別是等式型別；例如 int string、b bool c char 和 unit 是等式型別，而 int real 和 x real 不是。函式型別永遠不是等式型別，因為一般情況下不可能確定兩個函式是否等價。 型別宣告 型別宣告或同義詞（synonym）使用 type 關鍵字來定義。下面是給在平面中的點的型別宣告，計算兩點間距離，和透過海倫公式計算給定角點的三角形的面積的函式。 type loc real real fun heron a loc b loc c loc let fun dist x0 y0 x1 y1 let val dx x1 x0 val dy y1 y0 in Mathsqrt dx dx dy dy end val ab dist a b val bc dist b c val ac dist a c val s ab bc ac 20 in Mathsqrt s s ab s bc s ac end 資料型別 Standard ML 提供了對代數資料型別（ADT）的強力支援。一個 ML 資料型別可以被當作是元組的不交併（之和）。資料型別使用 datatype 關鍵字來定義，比如： datatype int_or_string INT of int STRING of string NEITHER 這個資料型別宣告建立一個全新的資料型別 int_or_string，還有一起的新構造子（一種特殊函式或值）INT、STRING 和 NEITHER；每個這種型別的值都是要麼 INT 具有一個整數，要麼 STRING 具有一個字串，要麼 NEITHER。寫為如下這樣： val i INT 3 val s STRING qq val n NEITHER val INT j i 這裡最後的一個宣告透過模式匹配，將變數 j 繫結到變數 i 所繫結的整數 3。val 模式 表示式是繫結的一般形式，它是良好定義的，當且僅當模式和表示式有相同的型別。 資料型別可以是多型的： datatype a pair PAIR of a a 這個資料型別宣告建立一個新的型別 a pair 家族，比如 int pair，string pair 等等。 資料型別可以是遞迴的： datatype int_list EMPTY INT_LIST of int int_list 這個資料型別宣告建立一個新型別 int_list，這個型別的每個值是要麼 EMPTY（空列表），要麼是一個整數和另一個 int_list 的接合。 透過 datatype 建立的型別是等式型別，如果它的所有變體，要麼是沒有引數的空構造子，要麼是有等式型別引數的構造子，並且在多型型別情況下所有型別引數也都是等式型別。遞迴型別在有可能情況下是等式型別，否則就不是。 列表 基礎庫提供的複雜資料型別之一是列表 list。它是一個遞迴的、多型的資料型別，可以等價的定義為： datatype a list nil of a a list 這裡的：是中綴算符，例如 3 4 5 nil 是三個整數的列表。列表是 ML 程式中最常用的資料型別之一，語言還為生成列表提供了特殊表示法 3 4 5。 real list 當然不是等式型別。但是沒有 int list 不能是等式型別的理由，所以它就是等式型別。注意型別變數不同就是不同的列表型別，比如 nil int list nil char list 是無效的，因為兩個表示式有不同的型別，即使它們有相同的值。但是 nil nil 和 nil int list nil 都是有效的。 基本庫 rev 函式 反轉 一個列表中的元素。它的型別是 a list a list，就是說它可以接受其元素有任何型別的列表，並返回相同型別的列表。更準確的說，它返回其元素相較於給定列表是反轉次序的一個新列表，比如將 a b c 對映成 c b a。中綴算符 表示兩個列表的串接。 rev 和 一般被實現為基本庫函式 revAppend 的簡單應用： fun revAppend l l revAppend x r l revAppend r x l fun rev l revAppend l fun l1 l2 revAppend rev l1 l2 模式匹配 Standard ML 的資料型別可以輕易的定義和用於程式設計，在很大程度上是由它的模式匹配，還有多數 Standard ML 實現的模式窮盡性檢查和模式冗餘檢查。 模式匹配可以在語法上嵌入到變數繫結之中，比如： val m int n int r real s real 2 3 20 30 type hyperlink protocol string address string title string val url hyperlink titleThe Standard ML Basis Library protocolhttps addresssmlfamilygithubioBasisoverviewhtml val protocolport addressaddr titlename url val x as fst snd 2 true 第一個 val 綁定了四個變數 m、n、r 和 s；第二個 val 綁定了一個變數 url；第三個 val 綁定了三個變數 port、addr 和 name，第四個叫分層模式，綁定了三個變數 x、fst 和 snd。 模式匹配可以在語法上嵌入到函式定義中，比如： datatype shape Circle of loc real 中心和弧度 Square of loc real 左上角和邊長，座標軸對齊 Triangle of loc loc loc 角點 fun area Circle _ r 314 r r area Square _ s s s area Triangle a b c heron a b c 次序在模式匹配中是緊要的：模式按文字次序來依次進行匹配。在特定計算中，使用下劃線_，來省略不需要它的值的子成員，這也叫做萬用字元（wildcard）模式。所謂的 子句形式 風格的函式定義，這裡的模式緊隨在函式名字之後出現，只是如下形式的一種語法糖： fun area shape case shape of Circle _ r 314 r r Square _ s s s Triangle a b c heron a b c 模式窮盡性檢查將確保資料型別的每個情況都已經顧及到。 如果有遺漏，則產生警告。 如果模式存在冗餘，也會導致一個編譯時間警告。 高階函式 函式可以接受函式作為實際引數，比如： fun applyToBoth f x y f x f y 函式可以產生函式作為返回值，比如： fun constantFn k fn anything k 函式可以同時接受和產生函式，比如： fun compose f g fn x f g x 基本庫的函式 Listmap，是在 Standard ML 中最常用的 Curry 化高階函式，它在概念上可定義為： fun map _ map f x xs f x map f xs 在基本庫中將函式複合定義為多型中綴算符 f o g，map 和 fold 高階函式有較高效率的實現。 例外 例外可以使用 raise 關鍵字發起，並透過模式匹配 handle 構造來處理： exception Undefined fun max x x max x xs let val m max xs in if x m then x else m end max raise Undefined fun main xs let val msg InttoString max xs handle Undefined empty listthere is no max in print msg n end 這裡的 是字串串接算符。可以利用例外系統來實現非區域性退出，例如這個函式所採用技術： exception Zero fun listProd ns let fun p 1 p 0 _ raise Zero p h t h p t in p ns handle Zero 0 end Zero 在 0 情況下發起，控制從函式 p 中一起出離。 引用 初始化基礎庫還以引用的形式提供了可變的儲存。引用 ref 可以在某種意義上被認為是如下這樣定義的： datatype a ref ref of a 還定義了內建的 函式來修改引用的內容，和！函式來檢索引用的內容。階乘函式可以使用引用定義為指令式風格： fun factImperative n let val i ref n and acc ref 1 in while i 0 do acc acc i i i 1 acc end 這裡使用圓括號對以；分隔的表示式進行了順序複合。 可變型別 a ref 是等式型別，即使它的成員型別不是。兩個引用被稱為是相等的，如果它們標識相同的 ref 單元，就是說相同的對 ref 構造子呼叫生成的同一個指標。因此 ref 1 ref 1 和 ref 10 ref 10 都是有效的，並且都求值為 false，因為即使兩個引用碰巧指向了相同的值，引用自身是分立的，每個都可以獨立於其他而改變。 模組語言 模組是 ML 用於構造大型專案和庫的系統。 模組 一個模組構成自一個簽名（signature）檔案和一個或多個結構檔案。簽名檔案指定要實現的 API（就像 C 語言標頭檔案或 Java 介面檔案）。結構實現這個簽名（就像 C 語言原始檔或 Java 類檔案）。頂層直譯器透過 use 命令匯入它們。ML 的標準庫被實現為這種方式的模組。 例如，下列定義一個算術簽名： signature ARITH sig eqtype t val zero t val one t val fromInt int t val fromIntPair int int t val fromReal real t val succ t t val pred t t val t t val t t t val t t t val t t t val t t t val t t bool val t t bool val t t bool val t t bool val t t bool val t t bool end 將上述內容存入 arithsig 檔案中。下面是這個簽名使用有理數的實現： structure Rational ARITH struct datatype t Rat of int int val zero Rat 0 1 val one Rat 1 1 fun fromInt n Rat n 1 fun ineg a b a b fun fromIntPair num den let fun reduced_fraction numerator denominator let fun gcd n 0 n gcd n d gcd d n mod d val d gcd numerator denominator in if d 1 then numerator div d denominator div d else numerator denominator end in if num 0 andalso den 0 then Rat reduced_fraction num den else if num 0 then Rat ineg reduced_fraction num den else if den 0 then Rat ineg reduced_fraction num den else Rat reduced_fraction num den end val SOME maxInt IntmaxInt val minPos 10 real maxInt fun fromReal r let fun convergent i f h_1 k_1 h_2 k_2 if i 0 andalso h_1 maxInt h_2 div i orelse k_1 maxInt k_2 div i then h_1 k_1 else if f minPos then i h_1 h_2 i k_1 k_2 else convergent trunc 10 f RealrealMod 10 f i h_1 h_2 i k_1 k_2 h_1 k_1 in if r 00 then Rat ineg convergent trunc r RealrealMod r 1 0 0 1 else Rat convergent trunc r RealrealMod r 1 0 0 1 end fun succ Rat a b fromIntPair a b b fun pred Rat a b fromIntPair a b b fun add Rat a b Rat c d if b d then fromIntPair a c b else fromIntPair a d c b b d fun sub Rat a b Rat c d if b d then fromIntPair a c b else fromIntPair a d c b b d fun mul Rat a b Rat c d fromIntPair a c b d fun div_ Rat a b Rat c d fromIntPair a d b c fun gt Rat a b Rat c d if b d then a c else a d b c fun lt Rat a b Rat c d if b d then a c else a d b c fun neg Rat a b Rat a b fun eq Rat a b Rat c d b d andalso a c fun a neg a fun a b add a b fun a b sub a b fun a b mul a b fun a b div_ a b fun op a b eq a b fun a b not eq a b fun a b gt a b fun a b gt a b orelse eq a b fun a b lt a b fun a b lt a b orelse eq a b end 將上述內容村存入 rationalsml 檔案中。下面是在 SMLNJ 中這個結構的簡單用例： use arithsig opening arithsig signature ARITH sig eqtype t val zero t val one t val fromInt int t val fromIntPair int int t val fromReal real t val succ t t val pred t t val t t val t t t val t t t val t t t val t t t val t t bool val t t bool val t t bool val t t bool val t t bool val t t bool end val it unit use rationalsml opening rationalsml autoloading library SMLNJBASISbasiscm is stable library SMLNJBASISbasiscmbasiscommoncm is stable autoloading done structure Rational ARITH val it unit infix infix local open Rational in val c let val a fromIntPair 2 3 val b fromIntPair 4 6 in a b end end val c Rat 43 Rationalt structure R Rational structure R ARITH RfromReal 3245 val it Rat 649200 Rationalt Standard ML 只允許透過簽名函式同實現進行互動，例如不可以直接透過這個程式碼中的 Rat 來建立資料物件。結構塊對外部隱藏所有實現細節。這裡的：叫做透明歸屬（ascription），可以透過所繫結的變數見到此結構的資料內容，與之相對的是，它叫做不透明歸屬，此結構的資料內容對外完全不可見。比如上面用例有結果：，如果改為不透明歸屬則有結果：。 要用有理數進行實際上的數值計算，需要處理分數形式中分母快速增大導致溢位整數型別大小範圍等問題。 函子 函子接受指定簽名的一個結構作為引數，並返回一個結構作為結果，下面示例的函子能在 ARITH 簽名的結構上計算移動平均： signature MOVINGLIST sig type t structure Arith sig type t end val size t int val average t Aritht val fromList Aritht list t val move t Aritht t val expand t Aritht t val shrink t t val trunc t t end 將上述內容存入 movinglistsig 檔案。 functor MovingList S ARITH MOVINGLIST struct type t St list int St structure Arith S fun size ml t 2 ml fun average ml t 3 ml fun fromList l St list let val n length l val sum foldl S Szero l local open S in val m sum fromInt n end in if null l then raise Empty else l n m end fun move l n m t new St let val old Listnth l n 1 local open S in val m m new old fromInt n end in new l n m end fun expand l n m t new St let val n n 1 local open S in val m m new m fromInt n end in new l n m end fun shrink l n m t let val old Listnth l n 1 val n if n 2 then n 1 else 1 local open S in val m m m old fromInt n end in l n m end fun trunc l n m t Listtake l n n m end 將上述內容存入 movinglistsml 檔案中。下面是在 SMLNJ 中這個函子的簡單用例，承接前面章節的例子已經載入 arithsig 和 rationalsml： use movinglistsig opening movinglistsig signature MOVINGLIST sig type t structure Arith sig type t end val size t int val average t Aritht val fromList Aritht list t val move t Aritht t val expand t Aritht t val shrink t t val trunc t t end val it unit use movinglistsml opening movinglistsml autoloading autoloading done functor MovingList S sig eqtype t val zero t val one t val fromInt int t val fromIntPair int int t val fromReal real t val succ t t val pred t t val t t val t t t val t t t val t t t val t t t val t t bool val t t bool val t t bool val t t bool val t t bool val t t bool end sig type t structure Arith sig val size t int val average t Aritht val fromList Aritht list t val move t Aritht t val expand t Aritht t val shrink t t val trunc t t end val it unit structure R Rational structure R ARITH structure MLR MovingList Rational structure MLR MOVINGLIST val d MLRfromList RfromIntPair 4 5 RfromIntPair 2 3 val d Rat 45Rat 232Rat 1115 MLRt val d MLRexpand d RfromIntPair 5 6 val d Rat 56Rat 45Rat 233Rat 2330 MLRt val d MLRmove d RfromIntPair 7 8 val d Rat 78Rat 56Rat 45Rat 233Rat 301360 MLRt val d MLRshrink d val d Rat 78Rat 56Rat 45Rat 232Rat 4148 MLRt val d MLRtrunc d val d Rat 78Rat 562Rat 4148 MLRt 這個用例承上節示例，Rational 結構採用了透明歸屬，有結果如：。如果它改為不透明歸屬，則對應結果為：。 示例程式碼 下列例子使用了 Standard ML 的語法和語義。 素數 下面是求素數的試除法實現： fun prime n let fun isPrime l i let fun existsDivisor false existsDivisor x xs if i mod x 0 then true else if x x i then false else existsDivisor xs in not existsDivisor l end fun iterate acc i if i n then acc else if isPrime acc i then iterate acc i i 2 else iterate acc i 2 in if n 2 then else iterate 2 3 end 基本庫 find 和 exists 函式在不存在符合條件元素的時候會遍歷整個列表， 這裡採用了特殊化了的 existsDivisor，用以在後續元素都不滿足條件時立即結束運算。 下面是埃拉託斯特尼篩法實現： fun prime n let fun dropComposite acc _ _ rev acc dropComposite acc l as x xs j i if j n then ListrevAppend acc l else if x j then dropComposite x acc xs j i else if x j then dropComposite acc l j i i else dropComposite acc xs j i i fun init i let fun loop l i if i 2 then l else loop i l i 2 in loop i i 1 mod 2 end fun iterate acc rev acc iterate acc l as x xs if x x n then 2 ListrevAppend acc l else iterate x acc dropComposite xs x x x 2 in if n 2 then else iterate init n end 這裡基於列表的篩法實現符合埃拉託斯特尼的原初演算法。篩法還有基於陣列的直觀實現。 下面是其直譯器下命令列執行例項： fun printIntList l int list print StringconcatWith map InttoString l n val printIntList fn int list unit val _ printIntList prime 100 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 漢明數 正規數是形如 2icdot 3jcdot 5k 的整數，對於非負整數 i 、 j 和 k，它是可以整除 60max lceil i2rceiljk 的數。計算升序的正規數的演算法經由戴克斯特拉得以流行 ，理查德漢明最初提出了這個問題，故而這個問題被稱為 漢明問題，這個數列也因而被稱為漢明數。Dijkstra 計算這些數的想法如下： 漢明數的序列開始於數 1。 要加入序列中的數有下述形式：2h 3h 5h，這裡的 h 是序列已有的任意的漢明數。 因此，可以生成最初只有一個 1 的序列 H，並接著序列 2H 3H 5H，並以此類推。 示例漢明數程式程式碼，一般用來展示，確使計算只在需要時進行的純函數語言程式設計方式。 fun Hamming_number n let fun merge p q let fun revMerge acc p q if not null p andalso not null q then if hd p hd q then revMerge hd p acc tl p q else if hd p hd q then revMerge hd q acc p tl q else revMerge hd p acc tl p tl q else if null p then ListrevAppend q acc else ListrevAppend p acc in if null p then q else if null q then p else rev revMerge p q end fun mul m x if x n div m then SOME x m else NONE fun mapPrefix pred l let fun mapp acc rev acc mapp x xs acc case pred x of SOME a mapp xs a acc NONE rev acc in mapp l end fun mergeWith f m i merge f m i fun generate l let fun listMul m mapPrefix mul m l in foldl mergeWith listMul 2 3 5 end fun iterate acc l if hd l n div 2 then merge l acc else iterate merge l acc generate l in if n 0 then iterate 1 else end 產生指定範圍內的漢明數需要多輪運算，後面每輪中的三個列表元素乘積運算中都可能產生超出這個範圍的結果，它們不需要出現在後續的運算之中。 基本庫 mapPartial 函式與它所對映的函式，透過基於 Option 結構的 SOME 和 NONE 構造子的協定，可以將所對映函式認為不符合條件的元素或者結果排除掉，它會遍歷整個列表。 由於這個演算法採用升序列表，故而這裡將它改寫為 mapPrefix 函式，用來在特定條件不滿足條件就立即結束。下面是漢明數程式在直譯器下命令列執行例項： fun printIntList l int list print StringconcatWith map InttoString l n val printIntList fn int list unit val _ printIntList Hamming_number 400 1 2 3 4 5 6 8 9 10 12 15 16 18 20 24 25 27 30 32 36 40 45 48 50 54 60 64 72 75 80 81 90 96 100 108 120 125 128 135 144 150 160 162 180 192 200 216 225 240 243 250 256 270 288 300 320 324 360 375 384 400 續體傳遞風格例項 下面是（CPS）的高階函式 foldr 和 map 的實現，和達成一個整數列表的合計函式的簡單用例： fun foldr f b l let fun f2 k k b f2 a r k f2 r fn x k f a x in f2 l fn x x end fun map f l foldr fn x y f x y l fun sum l foldr fn x y x y 0 l 對於輸入 e1 e2 en，sum 函式等價於函式複合 fn x x o fn x e1 x o fn x e2 x o o fn x en x，它應用於 0 上得到 e1 e2 en 0。 SMLNJ 支援頭等物件的續體。頭等續體對一門語言而言是能完全控制指令執行次序的能力。它們可以用來跳轉到產生對當前函式呼叫的那個函式，或者跳轉到此前已經退出了的函式。頭等續體儲存了程式執行狀態，它不儲存程式資料，只儲存執行上下文。 排序演算法 排序演算法關注計算複雜度，特別是時間複雜度，基本庫函式的實現細節也要考慮在內，比如串接函式 ，它被實現為 fun l1 l2 revAppend rev l1 l2，除非必需的情況避免使用遍歷整個列表的 rev 和 length 函式。 透過比較於這些排序演算法的周知程序式程式設計語言比如 C 語言的實現，可以體察到 ML 在控制流程和列表資料結構上的相關限制，和與之相適應的採用尾遞迴的特色函數語言程式設計風格。 插入排序 下面是簡單的插入排序演算法的尾遞迴和等價的普通遞迴實現： 尾遞迴 普通遞迴 fun insertSort l let fun insert pred ins l let fun loop acc ListrevAppend acc ins loop acc l as x xs if pred ins x then ListrevAppend acc ins l else loop x acc xs in loop l end val rec ge fn x y x y in rev foldl insert ge l end fun insertSort l let fun insert pred ins ins insert pred ins l as x xs if pred ins x then ins l else x insert pred ins xs val rec ge fn x y x y in rev foldl insert ge l end x 儲存在形式引數 acc 對應的分配堆中 x 儲存在呼叫棧棧幀中 插入排序演算法是穩定的，它在輸入列表趨於正序的時候效能最佳，在輸入列表趨於反序的時候效能最差，因此在演算法實現中，需要 insert 函式所插入列表保持為反序，在插入都完成後經 rev 函式再反轉回正序。在預期輸入資料趨於隨機化或者預知它經過了反轉的情況下，可以採用保持要插入列表為正序的變體插入排序演算法實現，它在輸入列表趨於反序的時候效能最佳，在輸入列表趨於正序的時候效能最差，它比自適應實現少作一次全列表反轉。 採用 foldr 函式可以相應的保持要插入列表為正序，由於 fun foldr f b l foldl f b rev l，它等同於對反轉過的列表應用變體插入排序。 希爾排序 希爾排序演算法是對插入排序的改進，保持了，放棄了穩定性。 下面是希爾排序的實現： fun shellSort l let fun insert pred ins l let fun loop acc ListrevAppend acc ins loop acc l as x xs if pred ins x then ListrevAppend acc ins l else loop x acc xs in loop l end val rec lt fn x y x y fun insertSort insertSort x x insertSort x y if y x then y x else x y insertSort x y z xs let val x y z if y x then if z y then z y x else if z x then y z x else y x z else if z x then z x y else if z y then x z y else x y z in foldl insert lt x y z xs end fun group lol n let fun dup n let fun loop acc i if i 0 then acc else loop acc i 1 in loop n end fun loop accj lol ListrevAppend accj lol loop acci accj loop rev acci rev accj loop acci accj lol loop rev acci accj lol loop acci lol accj loop acci lol rev accj loop acci ls accj lol loop acci ls accj lol loop acci x xs ls accj l ls loop xs acci ls x l accj ls in loop lol dup n end val lol len foldl fn x l n x l n 1 0 rev l val incs 1 4 9 20 46 103 233 525 1182 2660 5985 13467 30301 68178 153401 345152 776591 1747331 3931496 8845866 19903198 44782196 100759940 226709866 510097200 val gap let val v len 3 div 4 val thold if v 0 then 1 else v fun loop acc h if hd h thold then acc else loop hd h acc tl h in loop incs end fun sort h lol map insertSort group lol h in hd foldl sort lol gap end 這裡採用的間隔序列是 OEIS ，即 begin smallmatrix leftlceil frac 15 left 9cdot left frac 94rightk1 4 right rightrceil end smallmatrix ，它是徳田尚之在 1992 年提出的。這個序列用遞推公式表示為：hk hk，這裡的 hk 225hk1 1 而 h1 1。假定一個列表的長度 s 位於序列兩個元素之間，即 hk1 hk s hk1，如果 hk tfrac nms，這裡的 n m，則選擇初始間隔為 hk，否則為 hk1。在這個閾值下，對於不同長度 s 的列表和對應的初始間隔 h，每個列表的這些初始子列表的平均長度 tfrac sh，約在 tfrac mn tfrac sh tfrac 94tfrac mn 範圍之內。間隔序列還可以採用 OEIS ，它是 Marcin Ciura 在 2001 年透過實驗得到的。 快速排序 下面是快速排序演算法的自頂向下實現： fun quickSort quickSort x x quickSort x y if x y then x y else y x quickSort x y z let val x y if x y then x y else y x val y z if y z then y z else z y val x y if x y then x y else y x in x y z end quickSort pivot xs let fun partition pred l let fun loop p q p q loop h t p q if pred h then loop t h p q else loop t p h q in loop l end val le gt partition fn x x pivot xs in quickSort le pivot quickSort gt end 基本庫 partition 函式實現對快速排序而言有不必要的反轉， 這裡採用了它的簡化改寫。在 ML 中快速排序應採用自底向上實現： fun quickSort l let fun partition pred l let fun loop p q p q loop h t p q if pred h then loop t h p q else loop t p h q in loop l end fun iterate acc acc iterate acc xs iterate acc xs iterate acc x xs iterate x acc xs iterate acc x y xs let val x y if x y then x y else y x in iterate x y acc xs end iterate acc x y z xs let val x y if x y then x y else y x val x y z if y z then x y z else if x z then x z y else z x y in iterate x y z acc xs end iterate acc pivot d xs let val le gt partition fn x x pivot d in iterate acc gt pivot le xs end in iterate l end 歸併排序 下面是歸併排序演算法的自底向上法實現： fun mergeSort l let fun init acc acc init acc x x acc init acc x y if x y then x y acc else y x acc init acc x y z xs let val x y z if x y then if y z then x y z else if x z then x z y else z x y else if x z then y x z else if y z then y z x else z y x in init x y z acc xs end fun mergeWith _ acc acc mergeWith _ acc p ListrevAppend p acc mergeWith _ acc q ListrevAppend q acc mergeWith cmp acc p ps q qs if cmp p q then mergeWith cmp p acc ps q qs else mergeWith cmp q acc p ps qs val mergeRev mergeWith fn x y x y val revMerge mergeWith fn x y x y fun iterate _ iterate x isRev if isRev then rev x else x iterate acc isRev let val merge if isRev then mergeRev else revMerge fun loop acci acci loop acci x rev x acci loop acci x y xs loop merge x y acci xs in iterate loop acc not isRev end in iterate init l false end 考慮輸入列表 x1 xi a0 a9 xj xn，這裡在 xi 和 xj 之間的 10 個 a，具有相同的值並且需要保持其下標表示的次序，這裡的 xi a 並且 xj a，並且在這個區段前後的元素總數都能被 3 整除，則它被分解成子列表的列表 Xm xj a8 a9 a5 a6 a7 a2 a3 a4 a0 a1 xi X1，這裡有 m n div 3；假定這 4 個含有 a 的子列表兩兩歸併，在歸併正序子列表的歸併條件 x y 下，能得到 X1 xi a4 a0 a9 a5 xj Xk；繼續推演下去，在歸併反序子列表的歸併條件 x y 下，能得到 Xh xj a0 a9 xi X1。可以看出這種歸併操作能夠保證排序演算法的穩定性，即具有相同值的元素之間的相對次序保持不變。 分解初始的子列表採用了插入排序，還可進一步增加其大小。歸併排序也有自頂向下實現。 堆排序 下面是堆排序的基於陣列的實現： fun heapSort l let val h ArrayfromList l val len Arraylength h fun get i Arraysub h i fun set i v Arrayupdate h i v fun siftdown i ins n let fun sift k let val l k 2 1 val r l 1 in if r n andalso get r get l then r else if l n then l else k end fun loop i let val j sift i in if j i orelse get j ins then set i ins else set i get j loop j end in loop i end fun heapify let fun loop i if i 0 then else siftdown i get i len loop i 1 in loop len div 2 1 end fun generate let fun loop acc i let val t get 0 in if i 0 then t acc else siftdown 0 get i i loop t acc i 1 end in if len 0 then else loop len 1 end in heapify generate end 在陣列實現中，siftdown 函式融合了插入和篩選功能，它首先在暫時位於堆頂的要插入的元素，和從堆頂節點左右子堆的兩個堆頂元素中篩選出的那個元素，二者中選擇出哪個適合作堆頂元素；如果要插入元素適合，則以它為新堆頂元素而結束整個過程，否則以篩選出元素為新堆頂元素，並自頂向下逐級處理提供了新堆頂元素的子堆，將要插入元素暫時作為其堆頂元素並對它繼續進行 siftdown；siftdown 只要到達了某個堆底，就插入要插入的元素而結束整個過程。 在提取堆頂元素生成結果列表時，先提取走堆頂元素的內容，再摘掉最後的堆底元素將它的內容暫時放置在堆頂，這時堆的大小也相應的減一，隨後的 siftdown 函式，篩選出新的堆頂元素，並把原最後堆底元素插入回堆中。 在 heapify 函式建造堆的時候，首先自列表中間將元素分為前後兩組，後組中的元素被視為只有一個元素的堆，然後從後往前處理前組中的元素，這時它的左右子節點已經是已有的堆或者為空，在其上進行 siftdown，從而合成一個新堆。建造堆也可以採用 siftup 函式來實現，它自第二個元素開始從前往後逐個處理列表元素，其前面是已有的堆，將這個新元素自堆底向上插入到這個堆中。 堆排序演算法也可以使用二叉樹資料結構來實現二叉堆： fun heapSort l let datatype a heap Nil Leaf of a Node of a int a heap a heap fun key Nil let val SOME a IntminInt in a end key Leaf k k key Node k _ _ _ k fun count Nil 0 count Leaf _ 1 count Node _ c _ _ c fun left Nil Nil left Leaf _ Nil left Node _ _ l _ l fun insert Nil x Leaf x insert Leaf k l if l k then Node l 2 Leaf k Nil else Node k 2 Leaf l Nil insert Node k _ Leaf l Nil r if r k then Node r 3 Leaf k Leaf l else if r l then Node k 3 Leaf r Leaf l else Node k 3 Leaf l Leaf r insert Node k c l r x let val k x if k x then k x else x k in if count l count r then Node k c 1 insert l x r else if x key l then Node k c 1 insert r x l else Node k c 1 l insert r x end fun extract Nil Nil extract Leaf _ Nil extract Node _ _ l Nil l extract Node _ c l r let val k key l val n left l in if n Nil then Node k c 1 r Nil else if key n key r then Node k c 1 extract l r else Node k c 1 r extract l end fun heapify let fun loop acc acc loop acc x xs loop insert acc x xs in loop Nil l end fun generate h let fun loop acc Nil acc loop acc h loop key h acc extract h in loop h end in generate heapify end 二叉樹實現不能直接訪問堆底元素，從而不適宜透過摘掉它使堆的大小減一。這裡的 insert 函式，在原堆頂元素和要插入元素中選擇適合者作為新的堆頂元素，將落選的另一個元素作為新的要插入元素，插入到利於保持這個堆平衡的那個子樹之中。這裡的 extract 函式只篩選不插入，它將堆的大小減一。 這裡的 insert 和 extract 函式也可以直接轉寫為等價的尾遞迴形式，與列表情況不同，只要樹結構能保持良好的平衡，採用尾遞迴形式就沒有太大的必要性。 在二叉樹實現下，也可以採用 siftdown 函式來初始建造堆，而不需要在節點中儲存關於樹狀態的統計資訊。 基數排序 下面是針對非負整數的基數排序演算法的實現： fun radixSort l let fun distribute l d let val t0 fun loop t let fun join acc i let val f case i of 1 1 t 2 2 t 3 3 t 4 4 t 5 5 t 6 6 t 7 7 t 8 8 t _ in if i 0 then acc else join ListrevAppend f acc i 1 end in join 8 end loop t x xs let val f0 f1 f2 f3 f4 f5 f6 f7 t val t case x div d mod 8 of 0 x f0 f1 f2 f3 f4 f5 f6 f7 1 f0 x f1 f2 f3 f4 f5 f6 f7 2 f0 f1 x f2 f3 f4 f5 f6 f7 3 f0 f1 f2 x f3 f4 f5 f6 f7 4 f0 f1 f2 f3 x f4 f5 f6 f7 5 f0 f1 f2 f3 f4 x f5 f6 f7 6 f0 f1 f2 f3 f4 f5 x f6 f7 7 f0 f1 f2 f3 f4 f5 f6 x f7 _ t0 in loop t xs end in loop t0 l end val SOME maxInt IntmaxInt val max foldl fn x y if x y then x else y 0 l fun iterate l d let val l distribute l d in if d maxInt div 8 1 orelse max div d div 8 0 then l else iterate l d 8 end in iterate l 1 end 這裡採用的基數是 2 的 3 次冪 8，程式碼所使用的列表元組大小與基數大小成正比，運算量與列表中元素的總數與最大數的位數的乘積成正比。 隨機數生成 編寫排序演算法進行測試除了使用簡單的數列， 測試用列表還可以使用線性同餘偽隨機數函式來生成： fun randList n seed let val randx ref seed fun lcg randx randx 421 1663 mod 7875 randx fun lcg randx randx 1366 150889 mod 714025 randx fun iterate acc i if i 0 then acc else iterate lcg acc i 1 in iterate n end 語言直譯器 定義和處理一個小型表示式語言是相對容易的： exception Err datatype ty IntTy BoolTy datatype exp True False Int of int Not of exp Add of exp exp If of exp exp exp fun typeOf True BoolTy typeOf False BoolTy typeOf Int _ IntTy typeOf Not e if typeOf e BoolTy then BoolTy else raise Err typeOf Add e1 e2 if typeOf e1 IntTy andalso typeOf e2 IntTy then IntTy else raise Err typeOf If e1 e2 e3 if typeOf e1 BoolTy then raise Err else if typeOf e2 typeOf e3 then raise Err else typeOf e2 fun eval True True eval False False eval Int n Int n eval Not e case eval e of True False False True _ raise Fail typechecking is broken eval Add e1 e2 let val Int n1 eval e1 val Int n2 eval e2 in Int n1 n2 end eval If e1 e2 e3 if eval e1 True then eval e2 else eval e3 fun exp_repr e let val msg case e of True True False False Int n InttoString n _ in msg n end 忽略 TypeOf 的返回值，它在型別錯誤時發起 Err fun evalPrint e ignore typeOf e print exp_repr eval e 將這段程式碼錄入檔案比如 exprlangsml，並在命令列下執行 sml exprlangsml，可以用如下在正確型別的和不正確型別上執行的例子，測試這個新語言： val e1 Add Int 1 Int 2 正確的型別 val e1 Add Int 1Int 2 exp val _ evalPrint e1 3 val e2 Add Int 1 True 不正確的型別 val e2 Add Int 1True exp val _ evalPrint e2 uncaught exception Err raised at exprlangsml25202523 註釋和附錄程式碼 參見 Standard ML 和它的實現： SMLNJ，由普林斯頓大學和貝爾實驗室聯合開發的實現，它具有工具 MLLex、MLYacc 和併發程式設計擴充套件 Concurrent ML。 MLton，嚴格遵循標準定義的強力的編譯器，它具有工具 MLLex、MLYacc 和 MLNLFFIGen。 HaMLet，是一個 SML 直譯器，由 Andreas Rossberg 在（MPISWS）編寫，意圖成為精確且無障礙的標準定義參考實現。 LunarML，產生 LuaJavaScript 程式碼的 Standard ML 編譯器。 OCaml，由法國國家資訊與自動化研究所（INRIA）維護，是一個 工業強度 的 ML 方言 OCaml is an industrial strength programming language supporting functional imperative and objectoriented styles Retrieved on January 2 2018，演化自最初用來實現 Coq 定理證明器的 Caml。 ，由薩爾蘭大學設計的 Alice ML，是基於 Standard ML 的函數語言程式設計語言，擴充套件了對併發、分散式和約束式程式設計的豐富支援。 ，由波士頓大學的 Hongwei Xi 和卡內基梅隆大學的提出的發展而來，它向 ML 擴充套件了依賴型別。 F，由微軟研究院（MSR）開發，是一個基於 OCaml 的一個以NET 為目標的程式語言。 F，由 MSR 和 INRIA 主導開發，是一個基於 ML 的依賴型別函數語言程式設計語言。 Futhark，由哥本哈根大學（DIKU）開發，是屬於 ML 家族的一個函式式、資料並行、陣列程式語言。 Ur，由麻省理工學院的 Adam Chlipala 開發，是語法基於 Standard ML 的專門用於 web 開發的一個函數語言程式設計語言。 CMLex 和 CMYacc，由卡內基梅隆大學的 Karl Crary 開發，是用於 Standard ML、OCaml 和 Haskell 的詞法分析器和語法解析器。 Alpaca，是一個受 ML 啟發的執行在 Erlang 上的函數語言程式設計語言。 延伸閱讀 引用 外部連結 SML Help cmlib Category 程式設計語言 Category 模式匹配程式語言 Category 學術的程式語言 Category1973 年建立的程式語言