ML 語言

、Miranda、Nemerle、OCaml、、Rust、Scala、Standard ML、Ur

wikibooks = Standard ML Programming

}}

ML（Meta Language：元語言），是一個函式式、指令式的通用的程式語言，它著稱於使用了多型的 Hindley–Milner 型別推論。ML 能自動的指定多數的型別，不要求顯式的型別標註，而且能夠確保型別安全，已經正式證明了有良好型別的 ML 程式不會導致執行時間型別錯誤。

ML 提供了對函式實際引數的模式匹配、垃圾回收、指令式程式設計、傳值呼叫和柯里化。它被大量的用於程式語言研究之中，並且是全面規定了的和使用形式語義驗證了的少數語言之一。它的型別和模式匹配使得它非常適合並且經常用於在其他形式語言上進行操作，比如在編譯器構造、自動化定理證明和形式驗證中。

歷史

在 1970 年代早期，ML 由愛丁堡大學的羅賓・米爾納及他人研製出來 | year=1996 | title=From LCF to HOL: a short history | url=http://www.cl.cam.ac.uk/~mjcg/papers/HolHistory.html | quote=Around 1973 Milner moved to Edinburgh University and established a project to build a successor to Stanford LCF, which was subsequently dubbed Edinburgh LCF. He initially hired Lockwood Morris and Malcolm Newey (both recent PhD graduates from Stanford) as research assistants. …… Milner, ably assisted by Morris and Newey, designed the programming language ML (an abbreviation for “Meta Language”). …… In 1975, Morris and Newey took up faculty positions at Syracuse University and the Australian National University, respectively, and were replaced by Chris Wadsworth and myself. The design and implementation of ML and Edinburgh LCF was finalised and the book “Edinburgh LCF” was written and published. In 1978, the first LCF project finished, Chris Wadsworth went off trekking in the Andes (returning to a permanent position at the Rutherford Appleton Laboratory) and I remained at Edinburgh supported by a postdoctoral fellowship and with a new research interest: hardware verification. | access-date=2021-02-27 | archive-date=2016-09-05 | archive-url=https://web.archive.org/web/20160905201847/http://www.cl.cam.ac.uk/~mjcg/papers/HolHistory.html }}，用於在定理證明器中開發證明策略。LCF 的語言是 “PPλ”，它是一階邏輯演算與有型別的多型 λ 演算的結合，以 ML 作為元語言。ML 的語法從 ISWIM 及其擴充套件實現 PAL 得到了啟發， ML 執行在 DEC-10/TOPS-10 主機的 Stanford LISP 1.6 下。

在 1980 年，於愛丁堡大學的 VAX/VMS 系統上開發了 ML 編譯器，它被稱為 “VAX ML”，以區別於 LCF 版本的 “DEC-10 ML”。VAX ML 的編譯器和執行時間系統二者，都是用 Pascal 書寫而建立在 “函式抽象機器”（FAM）之上 In Proceedings of the 1984 ACM Symposium on LISP and Functional Programming, pages 208–217, 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 | year=1996 | title=From LCF to HOL: a short history | url=http://www.cl.cam.ac.uk/~mjcg/papers/HolHistory.html | quote=In 1981, I moved to a permanent position as Lecturer at the University of Cambridge Computer Laboratory. ……Larry Paulson, recently graduated with a PhD from Stanford, was hired at Cambridge ……. About this time, and in parallel, G ́erard Huet ported the Edinburgh LCF code to Lelisp and MacLisp. Paulson and Huet then established a collaboration and did a lot of joint development of LCF by sending each other magnetic tapes in the post. …… Edinburgh LCF ran interpretively, but during Paulson and Huet’s collaboration an ML compiler was implemented that provided a speedup by a factor of about twenty. …… The resulting new LCF system was named “Cambridge LCF” and completed around 1985. Paulson did little work on it after that. Mikael Hedlund (of the Rutherford Appleton Laboratory) then ported Cambridge LCF to Standard ML (using a new implementation of ML that he created). The resulting Standard ML based version of Cambridge LCF is documented …… in Paulson’s 1987 book Logic and Computation. | access-date=2021-02-27 | archive-date=2016-09-05 | archive-url=https://web.archive.org/web/20160905201847/http://www.cl.cam.ac.uk/~mjcg/papers/HolHistory.html }}，進而劍橋大學的又用它開發了基於 Common Lisp 的 | year=1996 | title=From LCF to HOL: a short history | url=http://www.cl.cam.ac.uk/~mjcg/papers/HolHistory.html | quote=Whilst Paulson was designing and implementing Cambridge LCF, I was mainly concerned with hardware verification. …… The first version of the HOL system was created by modifying the Cambridge LCF parser and pretty-printer to support higher order logic concrete syntax. …… The core HOL system became stable in about 1988. A new release that consolidated various changes and enhancements called HOL88 was issued then. We were fortunate to receive support from DSTO Australia to document HOL and from Hewlett Packard to port it from Franz Lisp to Common Lisp (a job very ably done by John Carroll). …… In the late 1980’s Graham Birtwistle of the University of Calgary started a project to reimplement HOL in Standard ML. The work was done by Konrad Slind, under Birtwistle’s direction and with the collaboration of the HOL group at Cambridge. The resulting system, called HOL90, was first released around 1990. …… Recently John Harrison and Konrad Slind have entirely reworked the design of HOL ……. …… This new version of HOL is called “HOL Light”. It is implemented in Caml Light and runs on modest platforms (e.g. standard PCs). It is faster than the Lisp-based HOL88, but a bit slower than HOL90 running in modern implementations of Standard ML. | access-date=2021-02-27 | archive-date=2016-09-05 | archive-url=https://web.archive.org/web/20160905201847/http://www.cl.cam.ac.uk/~mjcg/papers/HolHistory.html }}。

在 1983 年，Robin Milner 由兩個動機驅使開始重新設計 ML。其一是愛丁堡大學的及其小組在規定上的工作，具體化為規定語言 CLEAR，和表達可執行規定的函式式語言 HOPE。這項工作與 ML 有關的是兩方面成果：首先，HOPE 擁有優雅的程式設計特徵，特別是模式匹配，和子句式函式定義；其次，是使用在介面中的簽名，進行規定的模組化構造的想法。其二是在 VAX ML 上的工作，透過增加命名記錄和可變型別，擴充套件了 ML 中資料型別的品目。

在 1984 年，貝爾實驗室的 David MacQueen 提出了對 Standard ML 的模組系統的設計。在 Standard ML 的持續設計期間，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） LFP '86: Proceedings of the 1986 ACM conference on LISP and functional programming, Pages 266–278.，利用 Le Lisp 的執行時間系統重新實現了 Le_ML，並正式命名為 Caml Research Report RT-0121, INRIA. pp.491.。在 1990 年和 1991 年，INRIA 的基於用 C 實現的位元組碼直譯器，利用提供的記憶體管理系統重新實現了 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，比如 SML/NJ：

$ sml

Standard ML of New Jersey v110.79 built: Mon Apr 22 10:14:55 2024

-

可以在提示符 - 後鍵入程式碼。例如計算 1 + 2 * 3:

1 + 2 * 3;

val it = 7 : int

it;

val it = 7 : int

頂層推論這個表示式的型別為 int 並給出結果 7。如果輸入不完全，直譯器會列印第二提示符 =，這時通常可以用；終結輸入。it 是給未指定變數的表示式的標準變數。輸入 control-C 可返回直譯器頂層，輸入 control-D 可退出直譯器。可以使用第三方工具 rlwrap 執行 SML/NJ 直譯器，它處理使用者輸入並提供 readline 的行編輯、持久歷史和補全功能。

下面是輸出 hello, world! 的例子程式碼，在 SML/NJ 直譯器下執行它：

print "Hello, world!\n";

Hello, world!

val it = : unit

val _ = print "Hello, world!\n";

Hello, world!

使用 MLton 編譯器進行編譯執行它：

$ echo 'print "Hello, world!\n";' > hello-world.sml

$ mlton hello-world.sml

$ ./hello-world

Hello, world!

使用 LunarML 源到源編譯器，將前面的例子編譯成 Javascript 程式碼並使用 Node.js 執行它:

$ echo 'print "Hello, world!\n";' > hello-world.sml

$ lunarml compile --nodejs hello-world.sml

$ nodejs hello-world.mjs

Hello, world!

核心語言

不同於純函數語言程式設計語言，ML 是兼具一些指令式特徵的函數語言程式設計語言。ML 的特徵包括：傳值呼叫的求值策略，頭等函式，帶有垃圾收集的自動記憶體管理，引數多型，靜態型別，型別推論，代數資料型別，模式匹配和例外處理。不同於 Haskell，ML 與大多數程式語言一樣使用及早求值。

用 ML 書寫的程式構成自要被求值的，而非語句或命令，儘管一些表示式返回一個平凡的 unit 值並且只為其副作用而求值。以下章節介紹採用 Standard ML 的語法和語義。

3.1 函式

就像所有的函式式語言一樣，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

3.2 型別

有一些基本型別可以當作是 “內建” 的，因為它們是在 Standard ML 標準基本庫中預先定義的，並且語言為它們提供表示法，比如 34 是整數，而 "34" 是字串。一些最常用的基本型別是：

int 整數，比如 3 或～12。注意波浪號～表示負號。
real 浮點數，比如 4.2 或～6.4。Standard ML 不隱含的提升整數為浮點數，因此表示式 2 + 5.67 是無效的。
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 = 5.0, b = "five"} 有型別 {a : real, b : string}，這同於型別 {b : string, a : real}。可以使用 #a {a = 5.0, 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。第二段建立中綴運算元 averagedwith，接著定義它為型別 int int -> int 的函式。最後的 intpair 函式的型別是 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 = > >=

3.3 等式型別

算符 = 和分別被定義為多型的等式和不等式。= 它確定兩個值是否相等，有著型別。這意味著它的兩個運算數必須有相同的型別，這個型別必須是等式型別（eqtype）。上述基本型別中除了 real 之外，int、real、string、char 和 bool 都是等式型別。

例如：3 = 3、"3" = "3"、#"3" = #"3" 和 true = true，都是有效的表示式並求值為 true；而 3 = 4 是有效表示式並求值為 false，3.0 = 3.0 是無效表示式而被編譯器拒絕。這是因為 IEEE 浮點數等式打破了 ML 中對等式的一些要求。特別是 nan 不等於自身，所以關係不是自反的。

元組和記錄型別是等式型別，當時且僅當它的每個成員型別是等式型別；例如 int string、{b : bool, c : char} 和 unit 是等式型別，而 int real 和 {x : real} 不是。函式型別永遠不是等式型別，因為一般情況下不可能確定兩個函式是否等價。

3.4 型別宣告

型別宣告或同義詞（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

Math.sqrt (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) / 2.0

in

Math.sqrt (s (s - ab) (s - bc) * (s - ac))

end

3.5 資料型別

Standard ML 提供了對代數資料型別（ADT）的強力支援。一個 ML 資料型別可以被當作是元組的不交併（之和）。資料型別使用 datatype 關鍵字來定義，比如：

datatype intorstring

= INT of int

STRING of string

NEITHER

這個資料型別宣告建立一個全新的資料型別 intorstring，還有一起的新構造子（一種特殊函式或值）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

INTLIST of int * intlist

這個資料型別宣告建立一個新型別 intlist，這個型別的每個值是要麼 EMPTY（空列表），要麼是一個整數和另一個 intlist 的接合。

透過 datatype 建立的型別是等式型別，如果它的所有變體，要麼是沒有引數的空構造子，要麼是有等式型別引數的構造子，並且在多型型別情況下所有型別引數也都是等式型別。遞迴型別在有可能情況下是等式型別，否則就不是。

3.6 列表

基礎庫提供的複雜資料型別之一是列表 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)

3.7 模式匹配

Standard ML 的資料型別可以輕易的定義和用於程式設計，在很大程度上是由它的模式匹配，還有多數 Standard ML 實現的模式窮盡性檢查和模式冗餘檢查。

模式匹配可以在語法上嵌入到變數繫結之中，比如：

val ((m: int, n: int), (r: real, s: real)) = ((2, 3), (2.0, 3.0))

type hyperlink = {protocol: string, address: string, title: string}

val url : hyperlink =

{title="The Standard ML Basis Library", protocol="https",

address="//smlfamily.github.io/Basis/overview.html"}

val {protocol=port, address=addr, title=name} = 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)) = 3.14 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) => 3.14 r r

Square (_, s) => s * s

Triangle (a, b, c) => heron (a, b, c)

模式窮盡性檢查將確保資料型別的每個情況都已經顧及到。

如果有遺漏，則產生警告。

如果模式存在冗餘，也會導致一個編譯時間警告。

3.8 高階函式

函式可以接受函式作為實際引數，比如：

fun applyToBoth f x y = (f x, f y)

函式可以產生函式作為返回值，比如：

fun constantFn k = (fn anything => k)

函式可以同時接受和產生函式，比如：

fun compose (f, g) = (fn x => f (g x))

基本庫的函式 List.map，是在 Standard ML 中最常用的 Curry 化高階函式，它在概念上可定義為：

fun map' _ =

map' f (x :: xs) = f x :: map f xs

在基本庫中將函式複合定義為多型中綴算符 (f o g)，map 和 fold 高階函式有較高效率的實現。

3.9 例外

例外可以使用 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 = (Int.toString (max xs))

handle Undefined => "empty list...there 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 中一起出離。

3.10 引用

初始化基礎庫還以引用的形式提供了可變的儲存。引用 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 1.0) = (ref 1.0) 都是有效的，並且都求值為 false，因為即使兩個引用碰巧指向了相同的值，引用自身是分立的，每個都可以獨立於其他而改變。

模組語言

模組是 ML 用於構造大型專案和庫的系統。

4.1 模組

一個模組構成自一個簽名（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 bool

val bool

end

將上述內容存入 arith.sig 檔案中。下面是這個簽名使用有理數的實現：

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 ((h1> (maxInt - h2) div i)

orelse (k1> (maxInt - k2) div i))

then (h1, k1)

else if f c) else (a d) > (b c)

fun lt (Rat (a, b), Rat (c, d)) =

if b = d then (a b = not (eq (a, b))

fun a > b = gt (a, b)

fun a >= b = (gt (a, b) orelse eq (a, b))

fun a

將上述內容村存入 rational.sml 檔案中。下面是在 SML/NJ 中這個結構的簡單用例：

use "./arith.sig";

opening ./arith.sig

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 bool

val bool

end

val it = : unit

use "./rational.sml";

opening ./rational.sml

autoloading

library $SMLNJ-BASIS/basis.cm is stable

library $SMLNJ-BASIS/(basis.cm):basis-common.cm 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 (4,3) : Rational.t

structure R = Rational;

structure R : ARITH

R.fromReal (3.245);

val it = Rat (649,200) : Rational.t

Standard ML 只允許透過簽名函式同實現進行互動，例如不可以直接透過這個程式碼中的 Rat 來建立資料物件。結構塊對外部隱藏所有實現細節。這裡的：叫做透明歸屬（ascription），可以透過所繫結的變數見到此結構的資料內容，與之相對的是:>，它叫做不透明歸屬，此結構的資料內容對外完全不可見。比如上面用例有結果：，如果改為不透明歸屬則有結果：。

要用有理數進行實際上的數值計算，需要處理分數形式中分母快速增大導致溢位整數型別大小範圍等問題。

4.2 函子

函子接受指定簽名的一個結構作為引數，並返回一個結構作為結果，下面示例的函子能在 ARITH 簽名的結構上計算移動平均：

signature MOVINGLIST =

sig

type t

structure Arith : sig

type t end

val size : t -> int

val average : t -> Arith.t

val fromList : Arith.t list -> t

val move : t * Arith.t -> t

val expand : t * Arith.t -> t

val shrink : t -> t

val trunc : t -> t

end

將上述內容存入 movinglist.sig 檔案。

functor MovingList (S: ARITH) : MOVINGLIST =

struct

type t = S.t list int S.t

structure Arith = S

fun size (ml : t) = #2 ml

fun average (ml : t) = #3 ml

fun fromList (l : S.t list) = let

val n = length l

val sum = foldl S.+ S.zero 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 : S.t) = let

val old = List.nth (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 : S.t) = 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 = List.nth (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) =

(List.take (l, n), n, m)

end

將上述內容存入 movinglist.sml 檔案中。下面是在 SML/NJ 中這個函子的簡單用例，承接前面章節的例子已經載入 arith.sig 和 rational.sml：

use "./movinglist.sig";

opening movinglist.sig

signature MOVINGLIST =

sig

type t

structure Arith : sig type t end

val size : t -> int

val average : t -> Arith.t

val fromList : Arith.t list -> t

val move : t * Arith.t -> t

val expand : t * Arith.t -> t

val shrink : t -> t

val trunc : t -> t

end

val it = : unit

use "./movinglist.sml";

opening movinglist.sml

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 bool

val bool

end) :

sig

type t

structure Arith :

val size : t -> int

val average : t -> Arith.t

val fromList : Arith.t list -> t

val move : t * Arith.t -> t

val expand : t * Arith.t -> 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 = MLR.fromList R.fromIntPair (4, 5), R.fromIntPair (2, 3);

val d = (Rat (4,5),Rat (2,3),2,Rat (11,15)) : MLR.t

val d = MLR.expand (d, R.fromIntPair (5, 6));

val d = (Rat (5,6),Rat (4,5),Rat (2,3),3,Rat (23,30)) : MLR.t

val d = MLR.move (d, R.fromIntPair (7, 8));

val d = (Rat (7,8),Rat (5,6),Rat (4,5),Rat (2,3),3,Rat (301,360)) : MLR.t

val d = MLR.shrink d;

val d = (Rat (7,8),Rat (5,6),Rat (4,5),Rat (2,3),2,Rat (41,48)) : MLR.t

val d = MLR.trunc d;

val d = (Rat (7,8),Rat (5,6),2,Rat (41,48)) : MLR.t

這個用例承上節示例，Rational 結構採用了透明歸屬，有結果如：。如果它改為不透明歸屬，則對應結果為：。

示例程式碼

下列例子使用了 Standard ML 的語法和語義。

5.1 素數

下面是求素數的試除法實現：

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

基本庫 find 和 exists 函式在不存在符合條件元素的時候會遍歷整個列表，

這裡採用了特殊化了的 existsDivisor，用以在後續元素都不滿足條件時立即結束運算。

下面是埃拉託斯特尼篩法實現：

fun prime n = let

fun dropComposite (acc, , , ) = rev acc

dropComposite (acc, l as x :: xs, j, i) =

if j > n then List.revAppend (acc, l)

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 n

then 2 :: List.revAppend (acc, l)

else iterate (x :: acc,

dropComposite (, xs, x x, x 2))

in

if n

這裡基於列表的篩法實現符合埃拉託斯特尼的原初演算法。篩法還有基於陣列的直觀實現。

下面是其直譯器下命令列執行例項：

fun printIntList (l: int list) =

= print ((String.concatWith "" (map Int.toString 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

5.2 漢明數

正規數是形如的整數，對於非負整數、和，它是可以整除的數。計算升序的正規數的演算法經由戴克斯特拉得以流行

.，理查德・漢明最初提出了這個問題，故而這個問題被稱為 “漢明問題”，這個數列也因而被稱為漢明數。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 q) :: acc, p, tl q)

else revMerge ((hd p) :: acc, tl p, tl q)

else if null p

then List.revAppend (q, acc)

else List.revAppend (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 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 ((String.concatWith "" (map Int.toString l)) ^"\n");

val printIntList = fn : int list -> unit

val = printIntList (Hammingnumber 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

5.3 續體傳遞風格例項

下面是（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)...)))。

SML/NJ 支援頭等物件的續體。頭等續體對一門語言而言是能完全控制指令執行次序的能力。它們可以用來跳轉到產生對當前函式呼叫的那個函式，或者跳轉到此前已經退出了的函式。頭等續體儲存了程式執行狀態，它不儲存程式資料，只儲存執行上下文。

5.4 排序演算法

排序演算法關注計算複雜度，特別是時間複雜度，基本庫函式的實現細節也要考慮在內，比如串接函式 @，它被實現為 fun l1 @ l2 = revAppend (rev l1, l2)，除非必需的情況避免使用遍歷整個列表的 rev 和 length 函式。

透過比較於這些排序演算法的周知程序式程式設計語言比如 C 語言的實現，可以體察到 ML 在控制流程和列表資料結構上的相關限制，和與之相適應的採用尾遞迴的特色函數語言程式設計風格。

5.4.1 插入排序

下面是簡單的插入排序演算法的尾遞迴和等價的普通遞迴實現：

插入排序演算法是穩定的，它在輸入列表趨於正序的時候效能最佳，在輸入列表趨於反序的時候效能最差，因此在演算法實現中，需要 insert 函式所插入列表保持為反序，在插入都完成後經 rev 函式再反轉回正序。在預期輸入資料趨於隨機化或者預知它經過了反轉的情況下，可以採用保持要插入列表為正序的變體插入排序演算法實現，它在輸入列表趨於反序的時候效能最佳，在輸入列表趨於正序的時候效能最差，它比自適應實現少作一次全列表反轉。

採用 foldr 函式可以相應的保持要插入列表為正序，由於 fun foldr f b l = foldl f b (rev l)，它等同於對反轉過的列表應用變體插入排序。

5.4.2 希爾排序

希爾排序演算法是對插入排序的改進，保持了，放棄了穩定性。

下面是希爾排序的實現：

fun shellSort l = let

fun insert pred (ins, l) = let

fun loop (acc,) =

List.revAppend (acc, ins)

loop (acc, l as x :: xs) =

if pred (ins, x)

then List.revAppend (acc, ins :: l)

else loop (x :: acc, xs)

in

loop (, l)

end

val rec lt = fn (x, y) => (x (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 ，即，它是徳田尚之在 1992 年提出的。這個序列用遞推公式表示為：hk = ⌈h'k⌉，這裡的 h'k = 2.25・h'k-1 + 1 而 h'1 = 1。假定一個列表的長度 s 位於序列兩個元素之間，即 hk-1 k ≤ s k+1，如果 hk ≤・s，這裡的 n ≤ m，則選擇初始間隔為 hk，否則為 hk-1。在這個閾值下，對於不同長度 s 的列表和對應的初始間隔 h，每個列表的這些初始子列表的平均長度，約在 ≤ 範圍之內。間隔序列還可以採用 OEIS ，它是 Marcin Ciura 在 2001 年透過實驗得到的。

5.4.3 快速排序

下面是快速排序演算法的自頂向下實現：

fun quickSort =

quickSort x = x

quickSort x, y =

if x (x

基本庫 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 (x

5.4.4 歸併排序

下面是歸併排序演算法的自底向上法實現：

fun mergeSort l = let

fun init (acc,) = acc

init (acc, x) = x :: acc

init (acc, x, y) =

if x (x> y))

val revMerge = mergeWith (fn (x, y) => (x

考慮輸入列表 x1, ..., xi, a0, ..., a9, xj, ..., xn，這裡在 xi 和 xj 之間的 10 個 a，具有相同的值並且需要保持其下標表示的次序，這裡的 xi > a 並且 xj ，並且在這個區段前後的元素總數都能被 3 整除，則它被分解成子列表的列表 Xm, ..., xj, a8, a9, a5, a6, a7, a2, a3, a4, a0, a1, xi, ..., X1，這裡有 m = n div 3；假定這 4 個含有 a 的子列表兩兩歸併，在歸併正序子列表的歸併條件 x 下，能得到 X1, ..., xi, a4, ..., a0, a9, ..., a5, xj, ..., Xk；繼續推演下去，在歸併反序子列表的歸併條件 x > y 下，能得到 Xh, ..., xj, a0, ..., a9, xi, ..., X1。可以看出這種歸併操作能夠保證排序演算法的穩定性，即具有相同值的元素之間的相對次序保持不變。

分解初始的子列表採用了插入排序，還可進一步增加其大小。歸併排序也有自頂向下實現。

5.4.5 堆排序

下面是堆排序的基於陣列的實現：

fun heapSort l = let

val h = Array.fromList l

val len = Array.length h

fun get i = Array.sub (h, i)

fun set i v = Array.update (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 (get l) then r

else if (l

在陣列實現中，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 = Int.minInt 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) = (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 函式來初始建造堆，而不需要在節點中儲存關於樹狀態的統計資訊。

5.4.6 基數排序

下面是針對非負整數的基數排序演算法的實現：

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)

6 => (#6 t)

_ =>

in

if i (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 = Int.maxInt

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，程式碼所使用的列表元組大小與基數大小成正比，運算量與列表中元素的總數與最大數的位數的乘積成正比。

5.5 隨機數生成

編寫排序演算法進行測試除了使用簡單的數列，

測試用列表還可以使用線性同餘偽隨機數函式來生成：

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

5.6 語言直譯器

定義和處理一個小型表示式語言是相對容易的：

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 "type-checking 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 => Int.toString n

_ => ""

in

msg ^ "\n"

end

( 忽略 TypeOf 的返回值，它在型別錯誤時發起 Err )

fun evalPrint e = (ignore (typeOf e); print (exp_repr (eval e)));

將這段程式碼錄入檔案比如 expr-lang.sml，並在命令列下執行 sml expr-lang.sml，可以用如下在正確型別的和不正確型別上執行的例子，測試這個新語言：

val e1 = Add (Int 1, Int 2); ( 正確的型別 )

val e1 = Add (Int 1,Int 2) : exp

val _ = evalPrint e1;

3

val e2 = Add (Int 1, True); ( 不正確的型別 )

val e2 = Add (Int 1,True) : exp

val _ = evalPrint e2;

uncaught exception Err

raised at: expr-lang.sml:25.20-25.23