ML語言

ML，是一個函式式、指令式的通用的程式語言，它著稱於使用了多型的Hindley–Milner型別推論。ML能自動的指定多數的型別，不要求顯式的型別標註，而且能夠確保型別安全，已經正式證明了有良好型別的ML程式不會導致執行時間型別錯誤。ML提供了對函式實際引數的模式匹配、垃圾回收、指令式程式設計、傳值呼叫和柯里化。它被大量的用於程式語言研究之中，並且是全面規定了的和使用形式語義驗證了的少數語言之一。它的型別和模式匹配使得它非常適合並且經常用於在其他形式語言上進行操作，比如在編譯器構造、自動化定理證明和形式驗證中。歷史.在1970年代早期，ML由愛丁堡大學的羅賓·米爾納及他人研製出來，用於在定理證明器中開發證明策略。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書寫而建立在“函式抽象機器”之上。在1982年，愛丁堡大學的Kevin Mitchell，用VAX ML重寫了VAX ML編譯器，隨即John Scott和加入了開發，在又進行一系列重寫改進之後，新的編譯器被稱為“Edinburgh ML”。在1981年，INRIA的，將最初的LCF ML適配到Multics系統的Maclisp下，並且增加了編譯器。這個實現被描述於INRIA內部文件“ML手冊”之中，它被開發者自稱為“Le_ML”。劍橋大學的用它開發了基於Franz Lisp的Cambridge LCF，進而劍橋大學的又用它開發了基於Common Lisp的第一版的。在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和哥本哈根大學的Lars Birkedal的工作，ML Kit第一版完成並開源發行。在1987年，INRIA的Ascánder Suárez，基於巴黎第七大學的的“”，利用Le Lisp的執行時間系統重新實現了Le_ML，並正式命名為Caml。在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：$ smlStandard ML of New Jersey v110.79 [built: Sun Nov 6 06:43:07 2022]可以在提示符codice_1後鍵入程式碼。例如計算codice_2:- 1 + 2 * 3;val it = 7 : int頂層推論這個表示式的型別為codice_3並給出結果codice_4。如果輸入不完全則列印第二提示符codice_5，這時經常可以用codice_6終結輸入。codice_7是給未指定變數的表示式的標準變數。輸入control-C可返回直譯器頂層，輸入control-D可退出直譯器。可以使用第三方工具codice_8執行SML/NJ直譯器，它處理使用者輸入並提供codice_9的行編輯、持久歷史和補全功能。下面是hello, world!的程式程式碼在SML/NJ直譯器下執行的結果：- val _ = print "Hello, world!\n";Hello, world!和使用MLton編譯器進行編譯執行的例子：$ echo 'print "Hello, world!\n";' &gt; hello-world.sml$ mlton hello-world.sml$ ./hello-worldHello, world!核心語言.不同於純函數語言程式設計語言，ML是兼具一些指令式特徵的函數語言程式設計語言。ML的特徵包括：傳值呼叫的求值策略，頭等函式，帶有垃圾收集的自動記憶體管理，引數多型，靜態型別，型別推論，代數資料型別，模式匹配和異常處理。不同於Haskell，ML與大多數程式語言一樣使用及早求值。用ML書寫的程式構成自要被求值的，而非語句或命令，儘管一些表示式返回一個平凡的codice_10值並且只為其副作用而求值。以下章節介紹採用Standard ML的語法和語義。函式.就像所有的函式式語言一樣，ML的關鍵特徵是函式，它被用於進行抽象。例如階乘函式用純ML可表達為：fun fac 0 = 1| fac n = n * fac (n - 1)這裡將階乘描述為遞迴函式，具有一個單一的終止基礎情況。它類似於在數學教科書見到的階乘描述。多數ML程式碼在設施和語法上類似於數學。憑藉型別推論編譯器能推匯出，codice_11接受整數codice_12作為實際引數，則形式引數codice_13也是整數型別codice_3，而codice_15的結果是整數codice_16，則函式codice_11的結果也是整數型別。函式codice_11接受一個整數的形式引數並返回一個整數結果，它作為一個整體從而有著“從整數到整數的函式”型別codice_19。函式及其形式引數的"型別"還可以用型別標註來描述，使用codice_20表示法，它可以被讀作表示式codice_21有型別codice_22，它是可選也可忽略的。使用型別標註，這個例子可重寫為如下：fun fac 0 = 1| fac (n : int) : int = n * fac (n - 1)這個函式還依賴於模式匹配，這是ML語言的重要部份。注意函式形式引數不必須在圓括號內但要用空格分隔。當一個函式的實際引數是codice_12，它將返回整數codice_16。對於所有其他情況，嘗試第二行。這是一個遞迴，並且再次執行這個函式直到達到基礎情況。它可以使用codice_25表示式重寫為：fun fac n = case nof 0 =&gt; 1| n =&gt; n * fac (n - 1)這裡codice_25介入了模式和對應表示式的序列。它還可以重寫為將識別符號繫結到lambda函式：val rec fac =fn 0 =&gt; 1| n =&gt; n * fac (n - 1)這裡的關鍵字codice_27介入了識別符號到值的繫結，codice_28介入了匿名函式的定義，它可以用在codice_29的位置上，但使用codice_30算符而非codice_5。繫結到遞迴的匿名函式需要使用codice_32關鍵字來指示。透過將主要工作寫入尾遞迴風格的內部迭代函式，藉助於語言編譯或解釋系統進行的尾呼叫最佳化，這個函式可以得以增進效能，它的呼叫棧不需要隨函式呼叫數目而成比例的增長。對這個函式可以採用向內部函式增加額外的“累加器”形式引數codice_33來實現：fun fact n = letfun fac (0, acc) = acc| fac (n, acc) = fac (n - 1, n * acc)infac (n, 1)endcodice_34表示式的值是在codice_35和codice_36之間表示式的值。這個遞迴函式的實現不保證能夠終止，因為負數實際引數會導致遞迴呼叫的無窮降鏈條件。更健壯的實現會在遞迴前檢查實際引數為非負數，並在有問題的情況，即codice_13是負數的時候，啟用異常處理：fun fact n = letfun fac (0, acc) = acc| fac (n, acc) = fac (n - 1, n * acc)inif (n  ""inmsg ^ "\n"end(* 忽略TypeOf的返回值，它在型別錯誤時發起Err *)fun evalPrint e = (ignore (typeOf e); print (exp_repr (eval e)));將這段程式碼錄入檔案比如codice_301，並在命令列下執行codice_302，可以用如下在正確型別的和不正確型別上執行的例子，測試這個新語言：- 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 Errraised at: expr-lang.sml:25.20-25.23註釋和附錄程式碼.