ML语言

ML（Meta Language：元语言），是一个函数式、指令式的通用的编程语言，它著称于使用了多态的Hindley–Milner类型推论。ML能自动的指定多数的类型，不要求显式的类型标注，而且能够确保类型安全，已经正式证明了有良好类型的ML程序不会导致运行时间类型错误。
ML提供了对函数实际参数的模式匹配、垃圾回收、指令式编程、传值调用和柯里化。它被大量的用于编程语言研究之中，并且是全面规定了的和使用形式语义验证了的少数语言之一。它的类型和模式匹配使得它非常适合并且经常用于在其他形式语言上进行操作，比如在编译器构造、自动化定理证明和形式验证中。
历史.
在1970年代早期，ML由爱丁堡大学的罗宾·米尔纳及他人研制出来</ref>，用于在定理证明器中开发证明策略。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.</ref>。在1982年，爱丁堡大学的Kevin Mitchell，用VAX ML重写了VAX ML编译器，随即John Scott和加入了开发，在又进行一系列重写改进之后，新的编译器被称为“Edinburgh ML”。
在1981年，INRIA的，将最初的LCF ML适配到Multics系统的Maclisp下，并且增加了编译器。这个实现被描述于INRIA内部文档“ML手册”之中</ref>，它被开发者自称为“Le_ML”。剑桥大学的在1985年用它开发了基于Franz Lisp的Cambridge LCF</ref>，进而剑桥大学的又用它开发了基于Common Lisp的</ref>。
在1983年，Robin Milner由两个动机驱使开始重新设计ML。其一是爱丁堡大学的及其小组在规定上的工作，具体化为规定语言CLEAR</ref>，和表达可执行规定的函数式语言HOPE。这项工作与ML有关的是两方面成果：首先，HOPE拥有优雅的编程特征，特别是模式匹配，和子句式函数定义；其次，是使用在接口中的签名，进行规定的模块化构造的想法。其二是在VAX ML上的工作，通过增加命名记录和可变类型，扩展了ML中数据类型的品目</ref>。
在1984年，贝尔实验室的David MacQueen提出了对Standard ML的模块系统的设计。在Standard ML的持续设计期间，Edinburgh ML被渐进的修改，充当了Standard ML的近似原型实现</ref>。在1986年，普林斯顿大学的和贝尔实验室的David MacQueen，以Edinburgh Standard ML作为起步开发环境</ref>，开始了专注于生成高质量机器代码的Standard ML of New Jersey的活跃开发</ref>。
在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.</ref>，利用Le Lisp的运行时间系统重新实现了Le_ML，并正式命名为Caml Report RT-0121, INRIA. pp.491.</ref>。在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增加了面向对象特征</ref>，从而形成了OCaml。
今天ML家族的两个主要的方言是Standard ML和OCaml。ML的实力大多被用于语言设计和操作，比如建构编译器、分析器、定理证明器，但是它作为通用语言也被用于生物信息和财务系统等领域。ML确立了静态类型函数式编程范型，从而在编程语言历史上占有显要地位，它的思想在影响了众多的语言，例如Haskell、Nemerle、和Elm。
解释与编译.
ML代码片段很容易通过将其录入到“顶层”来研习，它也叫作读取﹣求值﹣输出循环或REPL。这是打印结果或定义的表达式的推论类型的交互式会话。很多SML实现提供交互式REPL，比如SML/NJ：
<syntaxhighlight lang="console">
$ sml
Standard ML of New Jersey v110.79 Mon Apr 22 10:14:55 2024
</syntaxhighlight>
可以在提示符codice_1后键入代码。例如计算codice_2:
<syntaxhighlight lang="sml" highlight="2,4">
- 1 + 2 * 3;
val it = 7 : int
- it;
val it = 7 : int
</syntaxhighlight>
顶层推论这个表达式的类型为codice_3并给出结果codice_4。如果输入不完全，解释器会打印第二提示符codice_5，这时通常可以用codice_6终结输入。codice_7是给未指定变量的表达式的标准变量。输入control-C可返回解释器顶层，输入control-D可退出解释器。可以使用第三方工具codice_8运行SML/NJ解释器，它处理用户输入并提供codice_9的行编辑、持久历史和补全功能。
下面是输出hello, world!的例子代码，在SML/NJ解释器下执行它：
<syntaxhighlight lang="sml" highlight="2,3,5">
- print "Hello, world!\n";
Hello, world!
val it = () : unit
- val _ = print "Hello, world!\n";
Hello, world!
</syntaxhighlight>
使用MLton编译器进行编译执行它：
<syntaxhighlight lang="console">
$ echo 'print "Hello, world!\n";' > hello-world.sml
$ mlton hello-world.sml
$ ./hello-world
Hello, world!
</syntaxhighlight>
使用LunarML源到源编译器，将前面的例子编译成Javascript代码并使用Node.js执行它:
<syntaxhighlight lang="console">
$ echo 'print "Hello, world!\n";' > hello-world.sml
$ lunarml compile --nodejs hello-world.sml
$ nodejs hello-world.mjs
Hello, world!
</syntaxhighlight>
核心语言.
不同于纯函数式编程语言，ML是兼具一些指令式特征的函数式编程语言。ML的特征包括：传值调用的求值策略，头等函数，带有垃圾收集的自动内存管理，参数多态，静态类型，类型推论，代数数据类型，模式匹配和例外处理。不同于Haskell，ML与大多数编程语言一样使用及早求值。
用ML书写的程序构成自要被求值的，而非语句或命令，尽管一些表达式返回一个平凡的codice_10值并且只为其副作用而求值。以下章节介绍采用Standard ML的语法和语义。
函数.
就像所有的函数式语言一样，ML的关键特征是函数，它被用于进行抽象。例如阶乘函数用纯ML可表达为：
<syntaxhighlight lang="sml">
fun fac 0 = 1
</syntaxhighlight>
这里将阶乘描述为递归函数，具有一个单一的终止基础情况。它类似于在数学教科书见到的阶乘描述。多数ML代码在设施和语法上类似于数学。
凭借类型推论编译器能推导出，codice_11接受整数codice_12作为实际参数，则形式参数codice_13也是整数类型codice_3，而codice_15的结果是整数codice_16，则函数codice_11的结果也是整数类型。函数codice_11接受一个整数的形式参数并返回一个整数结果，它作为一个整体从而有着“从整数到整数的函数”类型codice_19。函数及其形式参数的"类型"还可以用类型标注（annotation）来描述，使用codice_20表示法，它可以被读作表达式codice_21有类型codice_22，它是可选也可忽略的。使用类型标注，这个例子可重写为如下：
<syntaxhighlight lang="sml">
fun fac 0 = 1
</syntaxhighlight>
这个函数还依赖于模式匹配，这是ML语言的重要部份。注意函数形式参数不必须在圆括号内但要用空格分隔。当一个函数的实际参数是codice_12，它将返回整数codice_16。对于所有其他情况，尝试第二行。这是一个递归，并且再次执行这个函数直到达到基础情况。它可以使用codice_25表达式重写为：
<syntaxhighlight lang="sml">
fun fac n = case n 
</syntaxhighlight>
这里codice_25介入了模式和对应表达式的序列。它还可以重写为将标识符绑定到lambda函数：
<syntaxhighlight lang="sml">
val rec fac =
</syntaxhighlight>
这里的关键字codice_27介入了标识符到值的绑定，codice_28介入了匿名函数的定义，它可以用在codice_29的位置上，但使用codice_30算符而非codice_5。绑定到递归的匿名函数需要使用codice_32关键字来指示。
通过将主要工作写入尾递归风格的内部迭代函数，借助于语言编译或解释系统进行的尾调用优化，这个函数可以得以增进性能，它的调用栈不需要随函数调用数目而成比例的增长。对这个函数可以采用向内部函数增加额外的“累加器”形式参数codice_33来实现：
<syntaxhighlight lang="sml">
fun fac n = let
</syntaxhighlight>
codice_34表达式的值是在codice_35和codice_36之间表达式的值。这个递归函数的实现不保证能够终止，因为负数实际参数会导致递归调用的无穷降链条件。更健壮的实现会在递归前检查实际参数为非负数，并在有问题的情况，即codice_13是负数的时候，启用例外处理：
<syntaxhighlight lang="sml">
fun fac n = let
</syntaxhighlight>
类型.
有一些基本类型可以当作是“内建”的，因为它们是在Standard ML标准基本库中预先定义的，并且语言为它们提供表示法，比如codice_38是整数，而codice_39是字符串。一些最常用的基本类型是：
包括上述基本类型的各种类型可以用多种方式组合。一种方式是元组，它是值的有序集合，比如表达式codice_66的类型是codice_67，而codice_68的类型是codice_69。可以使用codice_70这样的语法来提取元组的指定次序的成员。
有0元组codice_71，它的类型指示为codice_10。但是没有1元组，或者说在例子codice_73和codice_16之间没有区别，都有类型codice_3。元组可以嵌套，codice_76不同于codice_77和codice_78二者。前者的类型是codice_79，其他两个的类型分别是codice_80和codice_81。
组合值的另一种方式是记录。记录很像元组，除了它的成员是有名字的而非有次序的，例如codice_82有类型codice_83，这同于类型codice_84。可以使用codice_85这样的语法来选取记录的指定名字的字段。
Standard ML中的函数只接受一个值作为参数，而不是参数的一个列表，可以使用上述元组模式匹配来实际上传递多个参数。函数还可以返回一个元组。例如：
<syntaxhighlight lang="sml">
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)
</syntaxhighlight>
这里第一段创建了两个类型是codice_86的函数codice_87和codice_88。第二段创建中缀算子codice_89，接着定义它为类型codice_86的函数。最后的codice_91函数的类型是codice_92。
下列函数是多态类型的一个例子：
<syntaxhighlight lang="sml">
fun pair x = (x, x)
</syntaxhighlight>
编译器无法推论出的codice_93的特殊化了的类型，它可以是codice_92、codice_95甚至是codice_96。在Standard ML中，它可以简单指定为多态类型codice_97，这里的codice_98（读作“alpha”）是一个类型变量，指示任何可能的类型。在上述定义下，codice_99和codice_100都是有良好定义的，分别产生codice_101和codice_102。
SML标准基本库包括了重载标识符：codice_103、codice_1、codice_105、codice_106、codice_107、codice_108、codice_109、codice_110、codice_61、codice_59、codice_62、codice_60。标准基本库提供了多态函数：codice_115、codice_116、codice_117、codice_118、codice_119。中缀运算符可以有从缺省最低codice_12到最高codice_121的任何运算符优先级。标准基本库提供了如下内建中缀规定：
<syntaxhighlight lang="sml">
infix 7 * / div mod
infix 6 + - ^
infixr 5 :: @
infix 4 = <> > >= < <=
infix 3 := o
infix 0 before
</syntaxhighlight>
等式类型.
算符codice_5和codice_58分别被定义为多态的等式和不等式。codice_5它确定两个值是否相等，有着类型codice_125。这意味着它的两个运算数必须有相同的类型，这个类型必须是等式类型（codice_126）。上述基本类型中除了codice_43之外，codice_3、codice_43、codice_47、codice_50和codice_53都是等式类型。
例如：codice_133、codice_134、codice_135和codice_136，都是有效的表达式并求值为codice_54；而codice_138是有效表达式并求值为codice_55，codice_140是无效表达式而被编译器拒绝。这是因为IEEE浮点数等式打破了ML中对等式的一些要求。特别是codice_141不等于自身，所以关系不是自反的。
元组和记录类型是等式类型，当时且仅当它的每个成员类型是等式类型；例如codice_142、codice_143和codice_10是等式类型，而codice_145和codice_146不是。函数类型永远不是等式类型，因为一般情况下不可能确定两个函数是否等价。
类型声明.
类型声明或同义词（synonym）使用codice_147关键字来定义。下面是给在平面中的点的类型声明，计算两点间距离，和通过海伦公式计算给定角点的三角形的面积的函数。
<syntaxhighlight lang="sml">
type loc = real * real
fun heron (a: loc, b: loc, c: loc) = let
</syntaxhighlight>
数据类型.
Standard ML提供了对代数数据类型（ADT）的强力支持。一个ML数据类型可以被当作是元组的不交并（之和）。数据类型使用codice_148关键字来定义，比如：
<syntaxhighlight lang="sml">
datatype int_or_string
</syntaxhighlight>
这个数据类型声明建立一个全新的数据类型codice_149，还有一起的新构造子（一种特殊函数或值）codice_150、codice_151和codice_152；每个这种类型的值都是要么codice_150具有一个整数，要么codice_151具有一个字符串，要么codice_152。写为如下这样：
<syntaxhighlight lang="sml">
val i = INT 3
val s = STRING "qq"
val n = NEITHER
val INT j = i
</syntaxhighlight>
这里最后的一个声明通过模式匹配，将变量codice_156绑定到变量codice_157所绑定的整数codice_41。codice_159是绑定的一般形式，它是良好定义的，当且仅当模式和表达式有相同的类型。
数据类型可以是多态的：
<syntaxhighlight lang="sml">
datatype 'a pair
</syntaxhighlight>
这个数据类型声明建立一个新的类型codice_160家族，比如codice_161，codice_162等等。
数据类型可以是递归的：
<syntaxhighlight lang="sml">
datatype int_list
</syntaxhighlight>
这个数据类型声明建立一个新类型codice_163，这个类型的每个值是要么codice_164（空列表），要么是一个整数和另一个codice_163的接合。
通过codice_148创建的类型是等式类型，如果它的所有变体，要么是没有参数的空构造子，要么是有等式类型参数的构造子，并且在多态类型情况下所有类型参数也都是等式类型。递归类型在有可能情况下是等式类型，否则就不是。
列表.
基础库提供的复杂数据类型之一是列表codice_167。它是一个递归的、多态的数据类型，可以等价的定义为：
<syntaxhighlight lang="sml">
datatype 'a list
</syntaxhighlight>
这里的codice_168是中缀算符，例如codice_169是三个整数的列表。列表是ML程序中最常用的数据类型之一，语言还为生成列表提供了特殊表示法codice_170。
codice_171当然不是等式类型。但是没有codice_172不能是等式类型的理由，所以它就是等式类型。注意类型变量不同就是不同的列表类型，比如codice_173是无效的，因为两个表达式有不同的类型，即使它们有相同的值。但是codice_174和codice_175都是有效的。
基本库codice_176函数“反转”一个列表中的元素。它的类型是codice_177，就是说它可以接受其元素有任何类型的列表，并返回相同类型的列表。更准确的说，它返回其元素相较于给定列表是反转次序的一个新列表，比如将codice_178映射成codice_179。中缀算符codice_180表示两个列表的串接。
codice_176和codice_180一般被实现为基本库函数codice_183的简单应用：
<syntaxhighlight lang="sml">
fun revAppend ([], l) = l
fun rev l = revAppend(l, [])
fun l1 @ l2 = revAppend(rev l1, l2)
</syntaxhighlight>
模式匹配.
Standard ML的数据类型可以轻易的定义和用于编程，在很大程度上是由它的模式匹配，还有多数Standard ML实现的模式穷尽性检查和模式冗余检查。
模式匹配可以在语法上嵌入到变量绑定之中，比如：
<syntaxhighlight lang="sml">
val ((m: int, n: int), (r: real, s: real)) = ((2, 3), (2.0, 3.0))
val url : hyperlink =
val {protocol=port, address=addr, title=name} = url
val x as (fst, snd) = (2, true)
</syntaxhighlight>
第一个codice_27绑定了四个变量codice_185、codice_13、codice_187和codice_188；第二个codice_27绑定了一个变量codice_190；第三个codice_27绑定了三个变量codice_192、codice_193和codice_194，第四个叫分层模式，绑定了三个变量codice_195、codice_196和codice_197。
模式匹配可以在语法上嵌入到函数定义中，比如：
<syntaxhighlight lang="sml">
datatype shape
fun area (Circle (_, r)) = 3.14 * r * r
</syntaxhighlight>
次序在模式匹配中是紧要的：模式按文本次序来依次进行匹配。在特定计算中，使用下划线codice_198，来省略不需要它的值的子成员，这也叫做通配符（wildcard）模式。所谓的“子句形式”风格的函数定义，这里的模式紧随在函数名字之后出现，只是如下形式的一种语法糖：
<syntaxhighlight lang="sml">
fun area shape = case shape
</syntaxhighlight>
模式穷尽性检查将确保数据类型的每个情况都已经顾及到。
如果有遗漏，则产生警告。
如果模式存在冗余，也会导致一个编译时间警告。
高阶函数.
函数可以接受函数作为实际参数，比如：
<syntaxhighlight lang="sml">
fun applyToBoth f x y = (f x, f y)
</syntaxhighlight>
函数可以产生函数作为返回值，比如：
<syntaxhighlight lang="sml">
fun constantFn k = (fn anything => k)
</syntaxhighlight>
函数可以同时接受和产生函数，比如：
<syntaxhighlight lang="sml">
fun compose (f, g) = (fn x => f (g x))
</syntaxhighlight>
基本库的函数codice_199，是在Standard ML中最常用的Curry化高阶函数，它在概念上可定义为：
<syntaxhighlight lang="sml">
fun map' _ [] = []
</syntaxhighlight>
在基本库中将函数复合定义为多态中缀算符codice_200，codice_201和codice_202高阶函数有较高效率的实现。
例外.
例外可以使用codice_203关键字发起，并通过模式匹配codice_204构造来处理：
<syntaxhighlight lang="sml">
exception Undefined;
fun max = x
fun main xs = let
</syntaxhighlight>
这里的codice_205是字符串串接算符。可以利用例外系统来实现非局部退出，例如这个函数所采用技术：
<syntaxhighlight lang="sml">
exception Zero;
fun listProd ns = let
</syntaxhighlight>
codice_206在codice_12情况下发起，控制从函数codice_208中一起出离。
引用.
初始化基础库还以引用的形式提供了可变的存储。引用codice_209可以在某种意义上被认为是如下这样定义的：
<syntaxhighlight lang="sml">
datatype 'a ref = ref of 'a
</syntaxhighlight>
还定义了内建的codice_116函数来修改引用的内容，和codice_115函数来检索引用的内容。阶乘函数可以使用引用定义为指令式风格：
<syntaxhighlight lang="sml">
fun factImperative n = let
</syntaxhighlight>
这里使用圆括号对以codice_6分隔的表达式进行了顺序复合。
可变类型codice_213是等式类型，即使它的成员类型不是。两个引用被称为是相等的，如果它们标识相同的“codice_209单元”，就是说相同的对codice_209构造子调用生成的同一个指针。因此codice_216和codice_217都是有效的，并且都求值为codice_55，因为即使两个引用碰巧指向了相同的值，引用自身是分立的，每个都可以独立于其他而改变。
模块语言.
模块是ML用于构造大型项目和库的系统。
模块.
一个模块构成自一个签名（signature）文件和一个或多个结构文件。签名文件指定要实现的API（就像C语言头文件或Java接口文件）。结构实现这个签名（就像C语言源文件或Java类文件）。顶层解释器通过codice_219命令导入它们。ML的标准库被实现为这种方式的模块。
例如，下列定义一个算术签名：
<syntaxhighlight lang="sml">
signature ARITH =
sig
end
</syntaxhighlight>
将上述内容存入codice_220文件中。下面是这个签名使用有理数的实现：
<syntaxhighlight lang="sml">
structure Rational : ARITH =
struct
将上述内容村存入codice_221文件中。下面是在SML/NJ中这个结构的简单用例：
<syntaxhighlight lang="sml" highlight="2-25,27-33,35,45,47,49">
- use "./arith.sig";
./arith.sig
signature ARITH =
val it = () : unit
- use "./rational.sml";
./rational.sml
$SMLNJ-BASIS/basis.cm is stable
$SMLNJ-BASIS/(basis.cm):basis-common.cm is stable
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
</syntaxhighlight>
Standard ML只允许通过签名函数同实现进行交互，例如不可以直接通过这个代码中的codice_222来建立数据对象。结构块对外部隐藏所有实现细节。这里的codice_223叫做透明归属（ascription），可以通过所绑定的变量见到此结构的数据内容，与之相对的是codice_224，它叫做不透明归属，此结构的数据内容对外完全不可见。比如上面用例有结果：，如果改为不透明归属则有结果：。
要用有理数进行实际上的数值计算，需要处理分数形式中分母快速增大导致溢出整数类型大小范围等问题。
函子.
函子接受指定签名的一个结构作为参数，并返回一个结构作为结果，下面示例的函子能在codice_225签名的结构上计算移动平均：
<syntaxhighlight lang="sml">
signature MOVINGLIST = 
sig
end
</syntaxhighlight>
将上述内容存入codice_226文件。
<syntaxhighlight lang="sml">
functor MovingList (S: ARITH) : MOVINGLIST = 
struct
end
</syntaxhighlight>
将上述内容存入codice_227文件中。下面是在SML/NJ中这个函子的简单用例，承接前面章节的例子已经加载codice_220和codice_221：
<syntaxhighlight lang="sml" highlight="2-15,17-52,54,56,58,60,62,64,66">
- use "./movinglist.sig";
movinglist.sig
signature MOVINGLIST =
val it = () : unit
- use "./movinglist.sml";
movinglist.sml
done
functor MovingList(S: sig
val it = () : unit
- structure R = Rational;
structure R : ARITH
- structure MLR = MovingList (Rational);
structure MLR : MOVINGLIST
- val d = MLR.fromList (4, 5), R.fromIntPair (2, 3);
val d = ((4,5),Rat (2,3),2,Rat (11,15)) : MLR.t
- val d = MLR.expand (d, R.fromIntPair (5, 6));
val d = ((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 = ((7,8),Rat (5,6),Rat (4,5),Rat (2,3),3,Rat (301,360)) : MLR.t
- val d = MLR.shrink d;
val d = ((7,8),Rat (5,6),Rat (4,5),Rat (2,3),2,Rat (41,48)) : MLR.t
- val d = MLR.trunc d;
val d = ((7,8),Rat (5,6),2,Rat (41,48)) : MLR.t
</syntaxhighlight>
这个用例承上节示例，codice_230结构采用了透明归属，有结果如：。如果它改为不透明归属，则对应结果为：。
示例代码.
下列例子使用了Standard ML的语法和语义。
素数.
下面是求素数的试除法实现：
<syntaxhighlight lang="sml">
fun prime n = let
</syntaxhighlight>
基本库codice_231和codice_232函数在不存在符合条件元素的时候会遍历整个列表，
这里采用了特殊化了的codice_233，用以在后续元素都不满足条件时立即结束运算。
下面是埃拉托斯特尼筛法实现：
<syntaxhighlight lang="sml">
fun prime n = let
</syntaxhighlight>
这里基于列表的筛法实现符合埃拉托斯特尼的原初算法。筛法还有基于数组的直观实现。
下面是其解释器下命令行运行实例：
<syntaxhighlight lang="sml" highlight="3,5">
- 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
</syntaxhighlight>
汉明数.
正规数是形如formula_1的整数，对于非负整数formula_2、formula_3和formula_4，它是可以整除formula_5的数。计算升序的正规数的算法经由戴克斯特拉得以流行
.</ref>，理查德·汉明最初提出了这个问题，故而这个问题被称为“汉明问题”，这个数列也因而被称为汉明数。Dijkstra计算这些数的想法如下：
示例汉明数程序代码，一般用来展示，确使计算只在需要时进行的纯函数式编程方式</ref>。
<syntaxhighlight lang="sml">
fun Hamming_number n = let
</syntaxhighlight>
产生指定范围内的汉明数需要多轮运算，后面每轮中的三个列表元素乘积运算中都可能产生超出这个范围的结果，它们不需要出现在后续的运算之中。
基本库codice_240函数与它所映射的函数，通过基于codice_241结构的codice_242和codice_243构造子的协定，可以将所映射函数认为不符合条件的元素或者结果排除掉，它会遍历整个列表。
由于这个算法采用升序列表，故而这里将它改写为codice_244函数，用来在特定条件不满足条件就立即结束。下面是汉明数程序在解释器下命令行运行实例：
<syntaxhighlight lang="sml" highlight="3,5">
- fun printIntList (l: int list) =
= print ((String.concatWith " " (map Int.toString 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
</syntaxhighlight>
续体传递风格实例.
下面是（CPS）的高阶函数foldr和map的实现，和达成一个整数列表的合计函数的简单用例：
<syntaxhighlight lang="sml">
fun foldr' f b l = let
fun map' f l = 
fun sum l =
</syntaxhighlight>
对于输入codice_245，codice_87函数等价于函数复合codice_247，它应用于codice_12上得到codice_249。</ref>
SML/NJ支持头等对象的续体。头等续体对一门语言而言是能完全控制指令执行次序的能力。它们可以用来跳转到产生对当前函数调用的那个函数，或者跳转到此前已经退出了的函数。头等续体保存了程序执行状态，它不保存程序数据，只保存执行上下文。
排序算法.
排序算法关注计算复杂度，特别是时间复杂度，基本库函数的实现细节也要考虑在内，比如串接函数codice_180，它被实现为codice_251，除非必需的情况避免使用遍历整个列表的codice_176和codice_253函数。
通过比较于这些排序算法的周知过程式编程语言比如C语言的实现，可以体察到ML在控制流程和列表数据结构上的相关限制，和与之相适应的采用尾递归的特色函数式编程风格。
插入排序.
下面是简单的插入排序算法的尾递归和等价的普通递归实现：
插入排序算法是稳定的，它在输入列表趋于正序的时候性能最佳，在输入列表趋于反序的时候性能最差，因此在算法实现中，需要codice_254函数所插入列表保持为反序，在插入都完成后经codice_176函数再反转回正序。在预期输入数据趋于随机化或者预知它经过了反转的情况下，可以采用保持要插入列表为正序的变体插入排序算法实现，它在输入列表趋于反序的时候性能最佳，在输入列表趋于正序的时候性能最差，它比自适应实现少作一次全列表反转。
采用codice_256函数可以相应的保持要插入列表为正序，由于codice_257，它等同于对反转过的列表应用变体插入排序。
希尔排序.
希尔排序算法是对插入排序的改进，保持了，放弃了稳定性。
下面是希尔排序的实现：
<syntaxhighlight lang="sml">
fun shellSort l = let
</syntaxhighlight>
这里采用的间隔序列是OEIS ，即formula_6，它是徳田尚之在1992年提出的。这个序列用递推公式表示为：codice_258，这里的codice_259而codice_260。假定一个列表的长度codice_188位于序列两个元素之间，即codice_262，如果codice_263，这里的codice_264，则选择初始间隔为codice_265，否则为codice_266。在这个阈值下，对于不同长度codice_188的列表和对应的初始间隔codice_236，每个列表的这些初始子列表的平均长度codice_269，约在codice_270范围之内。间隔序列还可以采用OEIS ，它是Marcin Ciura在2001年通过实验得到的。
快速排序.
下面是快速排序算法的自顶向下实现：
<syntaxhighlight lang="sml">
fun quickSort [] = []
</syntaxhighlight>
基本库codice_271函数实现对快速排序而言有不必要的反转，
这里采用了它的简化改写。在ML中快速排序应采用自底向上实现：
<syntaxhighlight lang="sml">
fun quickSort l = let 
</syntaxhighlight>
归并排序.
下面是归并排序算法的自底向上法实现：
<syntaxhighlight lang="sml">
fun mergeSort l = let
</syntaxhighlight>
考虑输入列表codice_272，这里在codice_273和codice_274之间的codice_275个codice_276，具有相同的值并且需要保持其下标表示的次序，这里的codice_277并且<code>xj ，并且在这个区段前后的元素总数都能被codice_41整除，则它被分解成子列表的列表codice_279，这里有codice_280；假定这codice_281个含有codice_276的子列表两两归并，在归并正序子列表的归并条件codice_283下，能得到codice_284；继续推演下去，在归并反序子列表的归并条件codice_285下，能得到codice_286。可以看出这种归并操作能够保证排序算法的稳定性，即具有相同值的元素之间的相对次序保持不变。
分解初始的子列表采用了插入排序，还可进一步增加其大小。归并排序也有自顶向下实现。
堆排序.
下面是堆排序的基于数组的实现：
<syntaxhighlight lang="sml">
fun heapSort l = let
</syntaxhighlight>
在数组实现中，codice_287函数融合了插入和筛选功能，它首先在暂时位于堆顶的要插入的元素，和从堆顶节点左右子堆的两个堆顶元素中筛选出的那个元素，二者中选择出哪个适合作堆顶元素；如果要插入元素适合，则以它为新堆顶元素而结束整个过程，否则以筛选出元素为新堆顶元素，并自顶向下逐级处理提供了新堆顶元素的子堆，将要插入元素暂时作为其堆顶元素并对它继续进行codice_287；codice_287只要到达了某个堆底，就插入要插入的元素而结束整个过程。
在提取堆顶元素生成结果列表时，先提取走堆顶元素的内容，再摘掉最后的堆底元素将它的内容暂时放置在堆顶，这时堆的大小也相应的减一，随后的codice_287函数，筛选出新的堆顶元素，并把原最后堆底元素插入回堆中。
在codice_291函数建造堆的时候，首先自列表中间将元素分为前后两组，后组中的元素被视为只有一个元素的堆，然后从后往前处理前组中的元素，这时它的左右子节点已经是已有的堆或者为空，在其上进行codice_287，从而合成一个新堆。建造堆也可以采用codice_293函数来实现，它自第二个元素开始从前往后逐个处理列表元素，其前面是已有的堆，将这个新元素自堆底向上插入到这个堆中。
堆排序算法也可以使用二叉树数据结构来实现二叉堆：
<syntaxhighlight lang="sml">
fun heapSort l = let
</syntaxhighlight>
二叉树实现不能直接访问堆底元素，从而不适宜通过摘掉它使堆的大小减一。这里的codice_254函数，在原堆顶元素和要插入元素中选择适合者作为新的堆顶元素，将落选的另一个元素作为新的要插入元素，插入到利于保持这个堆平衡的那个子树之中。这里的codice_295函数只筛选不插入，它将堆的大小减一。
这里的codice_254和codice_295函数也可以直接转写为等价的尾递归形式，与列表情况不同，只要树结构能保持良好的平衡，采用尾递归形式就没有太大的必要性。
在二叉树实现下，也可以采用codice_287函数来初始建造堆，而不需要在节点中保存关于树状态的统计信息。
基数排序.
下面是针对非负整数的基数排序算法的实现：
<syntaxhighlight lang="sml">
fun radixSort l = let
</syntaxhighlight>
这里采用的基数是codice_299的codice_41次幂codice_301，代码所使用的列表元组大小与基数大小成正比，运算量与列表中元素的总数与最大数的位数的乘积成正比。
随机数生成.
编写排序算法进行测试除了使用简单的数列，
测试用列表还可以使用线性同余伪随机数函数来生成：
<syntaxhighlight lang="sml">
fun randList (n, seed) = let
</syntaxhighlight>
语言解释器.
定义和处理一个小型表达式语言是相对容易的：
<syntaxhighlight lang="sml">
exception Err;
datatype ty
datatype exp
fun typeOf (True) = BoolTy
fun eval (True) = True
fun exp_repr e = let
(* 忽略TypeOf的返回值，它在类型错误时发起Err *) 
fun evalPrint e = (ignore (typeOf e); print (exp_repr (eval e)));
</syntaxhighlight>
将这段代码录入文件比如codice_302，并在命令行下执行codice_303，可以用如下在正确类型的和不正确类型上运行的例子，测试这个新语言：
<syntaxhighlight lang="sml" highlight="2,4,6,9,10">
- 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
</syntaxhighlight>