单子 (函数式编程)

在函数式编程中，单子（monad）是一种抽象，它允许以泛型方式构造程序。支持它的语言可以使用单子来抽象出程序逻辑需要的。为了达成这个目标，单子提供它们自己的数据类型（每种类型的单子都有特定的类型），它表示一种特殊形式计算，与之在一起的有两个过程，一个过程用来包装单子内“任何”基本类型的值（产生单子值），另一个过程用来那些输出单子值的函数（叫做单子函数）。
单子的概念和术语二者最初都来自范畴论，这里的单子被定义为具有额外结构的函子。开始于1980年代晚期和1990年代早期的研究，确立了单子可以将看似完全不同的计算机科学问题置于一个统一的函数式模型之下。范畴论还提供了叫做单子定律的一些形式要求，任何单子都应当满足它并可以用它来验证单子代码。
通过单子，编程者可以把复杂的函数序列变成简洁的管道，它抽象出了辅助数据管理、控制流或副作用。单子可以简化范围宽广的问题，比如处理潜在（通过codice_1单子），或将值保持在一个灵活而形式正确的列表中（使用codice_2单子）。因为单子使得某种计算的语义明确，它们还可以用来实现便捷的语言特征。一些语言比如Haskell，甚至在它们的核心库中为通用单子结构提供预制的定义和常用实例。
概述.
单子可以通过定义一个类型构造子codice_3和两个运算即codice_4和codice_5来建立。C. A. McCann解释说：“对于单子codice_3，类型codice_7的值，表示可以在这个单子上下文内访问的类型codice_8的值。”
通过这些元素，编程者可以复合出一个函数调用的序列（管道），在一个表达式中通过一些bind算子把它们链接起来。每个函数调用转变它的输入普通类型值，而bind算子处理返回的单子值，它被填入到序列中下一个步骤。
在每对复合的函数调用之间，bind算子codice_13可以向单子值codice_7注入在函数codice_15内不可访问的额外信息，并沿着管道传递下去。它还可进行细致的执行流控制，比如只在特定条件下调用函数，或以特定次序执行函数调用。
Maybe单子例子.
下面的快捷伪代码例子展示编程者使用单子的动机。未定义的值或运算，是健壮的软件应当准备好做出优雅处理的一个特殊问题。
完成这个目标的第一步是建立一个可选类型，它标记一个值，要么承载某个类型codice_25（codice_25可以是任何类型）的值，要么没有承载值。新的类型将叫做codice_27，而这个类型的值，可以包含要么类型codice_25的值，要么空值codice_29。类型codice_25的值codice_31，若定义并用于codice_1上下文，则叫做codice_33。这么做是通过区分一个变量，是处在承载了有定义的值的情况，还是处在未定义的情况，来避免混淆。
<syntaxhighlight lang="haskell">
data Maybe T = Just T | Nothing
</syntaxhighlight>
codice_27可以被理解为一种“包装”类型，把类型codice_25包装成具有内建异常处理的一种新类型，尽管不承载关于异常成因的信息。
在下列的伪代码中，前缀着codice_3的变量有针对某种类型codice_25的类型codice_27。例如，如果变量codice_39包含一个值，那么它是codice_33，这里的变量codice_31有类型codice_25。codice_43是匿名函数，它的形式参数codice_31的类型是推论而来，而codice_45是函数复合算子。
另一个改进是，函数通过codice_1类型，能管理简单的检查异常：一旦某个步骤失败，就短路并返回codice_29；如果计算成功则，返回正确的值而无需再评论。
加法函数codice_48，在做二个codice_1值codice_39和codice_51的加法之时，就实现了上述改进，它可以如下这样定义：
<syntaxhighlight lang="haskell">
add :: Maybe Number -> Maybe Number -> Maybe Number
add mx my = ...
</syntaxhighlight>
书写函数来逐一处理codice_1值的各种情况可能相当枯燥，并且随着定义更多函数而变得更甚。将多个步骤链接起来的运算，是减轻这种状况的一种方式，通过使用中缀算子如codice_53，甚至可以直观的表示出，将每个步骤得出的（可能未定义的）结果，填入下一步骤之中。因为每个结果在技术上被插入到另一个函数之中，这个算子转而接受一个函数作为一个形式参数。由于codice_48已经指定了它的输出类型，保持这个算子的灵活性，而接受输出与其输入不同类型的函数应当没有什么伤害：
<syntaxhighlight lang="haskell">
Â»= :: Maybe T -> (T -> Maybe U) -> Maybe U
(mx Â»= f) = ...
</syntaxhighlight>
在具有codice_13可用时，codice_48可以被精制为更紧凑的表述：
<syntaxhighlight lang="haskell">
add mx my = 
</syntaxhighlight>
这更加简洁，而一点额外的分析就能揭示出它的强大之处。首先，codice_57在codice_48中扮演的唯一角色，就是将一个低层值标记（tag）为也是codice_1值。为了强调codice_57通过包装低层值而在其上施加作用，它也可以被精制为函数，比如叫做codice_61：
<syntaxhighlight lang="haskell">
eta :: T -> Maybe T
eta x = Just x
</syntaxhighlight>
整体情况是这两个函数codice_13和codice_61被设计用来简化codice_48，但是他们明显的不以任何方式依赖于codice_48的细节，只是有关于codice_1类型。这些函数事实上可以应用于codice_1类型的任何值和函数，不管底层的值的类型。例如，下面是来自Kleene三值逻辑的一个简洁的NOT算子，也使用了相同的函数来自动化未定义值：
<syntaxhighlight lang="haskell">
trinot :: Maybe Boolean -> Maybe Boolean
trinot mp = mp Â»= λp -> (eta ∘ not) p
</syntaxhighlight>
可以看出来codice_1类型，和与之一起的codice_13和codice_61，形成了单子。尽管其他单子会具体化不同的逻辑过程，而且一些单子可能有额外的属性，它们都有三个类似的构件（直接或间接的）服从这个例子的纲要。
定义.
对函数式编程中的单子的更常用的定义，比如在上面的例子中用到的，实际上基于了，而非范畴论的标准定义。两个构造可以证明在数学上是等价的，任何定义都能产生有效的单子。给定任何良好定义的基本类型codice_25、codice_72，单子构成自三个部份：
但要完全具备单子资格，这三部份还必须遵守一些定律：
在代数上，这意味任何单子都引起一个范畴（叫做）和在函子（从值到计算）的范畴上的幺半群，具有单子复合作为二元算子和codice_4作为单位元。
单子为有价值的技术提供了机会，超出了只是组织程序逻辑。单子可以为有用的语法特征奠定基础工作，而它们的高级和数学本质能实现重大的抽象。
语法糖.
尽管公开的使用codice_5通常就行得通，很多编程者偏好模仿指令式语句的语法（在Haskell中称为“do表示法”，在OCaml中称为“perform表示法”，在F♯中称为“计算表达式”，在Scala中称为“for推导式”）。这只是将单子管道伪装成代码块的语法糖；编译器会悄悄的将这些表达式转换成底层的函数式代码。
将上述的codice_1单子例子中的codice_48函数伪码转换成Haskell代码来用行动展示这个特征。非单子版本的codice_48用Haskell写出来如下这样：
<syntaxhighlight lang="haskell">
add mx my =
</syntaxhighlight>
在使用单子的Haskell中，codice_10是codice_4的标准名字，加上必须显式处置的lambda表达式，即使多了这些技术，codice_1单子使得定义更加清晰：
<syntaxhighlight lang="haskell">
add mx my =
</syntaxhighlight>
使用do表示法，可以进一步精炼成非常直观的序列：
<syntaxhighlight lang="haskell">
add mx my = do
</syntaxhighlight>
甚至通用单子定律自身都可以用do表示法来表达：
<syntaxhighlight lang="haskell">
</syntaxhighlight>
尽管方便，开发者应当记住这种块风格只是语法上的并可外观上替代为单子(甚至非单子的CPS）表达式。使用codice_5来表达单子管道仍在很多情况下是更加清晰的，一些函数式编程拥戴者提议，由于块风格允许初学者存续来自指令式编程的习惯，应当避免缺省的而只在明显更优越的时候使用它。
历史.
在编程中术语“单子”（monad）实际上最早可追溯至APL和J编程语言，它们趋向于是纯函数式的。但是，在这些语言中，“monad”仅是只接受一个形式参数的函数的简称（有二个形式参数的函数叫做“dyad”）。
数学家最初在1950年代晚期公式化单子概念（起绰号为“标准构造”），而术语“monad”成为主导要归功于范畴学家桑德斯·麦克兰恩。但是，上述的使用bind定义的形式，最初由数学家在1965年描述，用来证明任何单子都可以特征化为在两个（协变）函子之间的伴随。
开始于1980年代，单子模式的模糊概念在计算机科学社区中浮出水面。依据编程语言研究者Philip Wadler，计算机科学家于1970年代和1980年代早期，在他讨论的价值的时候，预见到了它的一些方面，范畴论作为形式语义学的丰富来源，和在值和计算之间的类型区别。研究性语言，它活跃设计直到1990年，还有效的将I/O基于在单子类型之上，但是这个联系在当时没有实现。
计算机科学家最早明确的将范畴论的单子联系于函数式编程，在1989年于讨论会论文之中，随后在1991年还有更加精制的期刊提交。在早期的工作中，一些计算机科学家使用范畴论推进为lambda演算提供语义。Moggi的关键洞察是真实世界程序不只是从值到另外的值的函数，而是形成在这些值之上计算的变换。在用范畴论术语形式化的时候，这导致的结果是单子作为表示这些计算的结构。
其他一些人以这个想法为基础并进行了推广，包括Philip Wadler和Simon Peyton Jones，二者都参与了Haskell规定。特别是，Haskell直到v1.2一直使用有问题的“惰性流”模型来将I/O调和于惰性求值，然后切换到了更灵活的单子接口。Haskell社区继续将单子应用于函数式编程的很多问题中，使用Haskell工作的研究者最终将单子模式推广成广泛的结构层级，包括应用式函子和。
首先，使用单子的编程很大程度上局限于Haskell及其派生者，但是由于函数式编程已经影响了其他编程范型，很多语言结合了单子模式（不这么称呼的话也在精神上）。其公式化现已存在于Scheme、Perl、Python、Racket、Clojure、Scala和F#之中，并已经被考虑用于新的ML标准。
用途.
单子模式的价值超出了只是压缩代码和提供到数序推理的联系。不管开发者采用的语言或缺省编程范型是什么，遵从单子模式都会带来纯函数式编程的很多利益。通过实化特定种类的计算，单子不仅封装了这个计算模式的冗长细节，而且它以声明式方式来这么做，增进了代码清晰性。因为单子值所显式代表的不只是计算出的值，而是计算出的作用（effect），单子表达式在上可以被替代为它们的值，非常像纯表达式能做到的那样，允许了基于重写的很多技术和优化。
典型的，编程者会使用codice_5来把单子函数链接成一个序列，这导致了一些人把单子描述为“可编程的分号”，参照众多指令式语言使用分号来分割语句。但是，需要强调单子实际上不确定计算的次序；甚至在使用它们作为中心特征的语言中，更简单的函数复合可以安排程序内的步骤。单子的一般效用准确的说在于简化程序的结构并通过抽象来增进关注点分离。
单子结构还可以被看作修饰模式的独特的数学和编译时间变种。一些单子可以传载对函数是不可访问的额外数据，而且一些单子甚至具有在执行上的更细致控制，例如只在特定条件下调用一个函数。因为它们让应用程序员实现领域逻辑，而卸载样板代码至预先开发的模块，单子甚至可以当作面向方面编程的工具。
单子的另一个值得注意的用途，是在其他方面都纯函数式的代码中，隔离副作用，比如输入/输出或可变的。即使纯函数式语言仍可以不使用单子来实现这些“不纯”计算，特别是通过对函数复合和续体传递风格（CPS）的错综复杂混合。但是使用单子，多数这些脚手架可以被抽象出去，本质上通过提取出在CPS代码中每个反复出现的模式并集束到一个独特的单子之中。
如果一个语言缺省的不支持单子，仍有可能实现这个模式，经常没有多少困难。在从范畴论转换成编程术语的时候，单子结构是并可以在支持限定的多态的等价特征的任何语言中直接定义。一个概念在操作底层数据类型时保持对操作细节不可知的能力是强大的，然而单子的独特特征和严格行为将它们同其他概念区别开来。
分析.
单子模式的利益之一是将数学上的精确性施加到编程逻辑上。不只是单子定律可以用来检查实例的有效性，而且来自有关结构（比如函子）的特征可以通过子类型来使用。
从函子推导.
尽管在计算机科学中少见，可以直接使用范畴论，它定义单子为有二个额外自然变换的函子。作为开始，一个结构要求叫做map的高阶函数（“泛函”）从而具备函子资格：
但是这不总是一个主要问题，尤其是在单子派生自预先存在的函子的时候，单子马上就自动继承codice_95。 出于历史原因，在Haskell中这个codice_95转而叫做codice_97。
单子的第一个变换实际上同于来自Kleisli三元组的codice_4，但是更密切的服从结构的层级，结果是codice_4特征化一个应用式函子，这是在单子和基本函子之间的中间结构。在应用式的上下文中，codice_4有时被称为codice_101，但是这仍是相同的函数。在这个构造中有不同的地方是定律codice_4必须满足；因为codice_5未定义，这个约束转而依据codice_95给出：
从应用式函子到单子的最后跳跃来自于第二个变换codice_20函数，在范畴论中这个自然变换通常叫做，它扁平化单子的嵌套应用：
作为特征性函数，codice_20必须还满足三个单子定律的变体：
不管开发者是否直接定义单子或Kleisli三元组，底层的结构都是相同的，二者形式可以轻易的相互导出：
例子：List单子.
[[File:Sixth root as a composite function of cbrt and sqrt.svg|thumb|right|复数多值平方和立方方根函数可以起来产生六次方根函数。支配输入和输出类型的结构和复合不同行动的结构，二者一起是list单子。弹丸符号•指示codice_5算子，codice_116是复数，方括号指示，而codice_117含义是定义为:
<syntaxhighlight lang="haskell">
lift(f) = f° := unit•f = f•unit
sqrt°(z) == append(map(unit,sqrt(z)))= append(map(sqrt,unit(z)))
sxrt(z) = (cbrt°•sqrt°)(z) == append(map(cbrt°,sqrt°(z)))
</syntaxhighlight>]]
codice_2单子天然的展示了如何手工的从更简单的函子导出单子。在很多语言中，列表结构与很多基本特征一起是预定义的，所以假定codice_2类型构造子和codice_120算子（用中缀表示法表示为codice_121）已经存在于这里了。
将一个平常的值嵌入到列表中在多数语言中也是微不足道的：
自此，通过列表推导式迭代的应用一个函数，看起来就是对codice_5的一个容易的选择，从而将列表转换成完全的单子。这个方式的困难在于codice_5预期一个单子函数，它在这种情况下会输出列表自身；随着更多函数的应用，嵌套的列表的层次会累加，要求不止一个基本推导式。
但是，在整个列表上应用任何“简单”函数的过程，也就是codice_95，就直截了当了：
现在，这两个过程已经将codice_2提升为应用式函子。要完全具备单子资格，只需要codice_20的一个正确的表示法来扁平化重复的结构，但是对于列表，这意味着去包装一个外部列表来包含着值的那些内部列表：
结果的单子不只是一个列表，而且在应用函数的时候可以自动调整大小和压缩自身。codice_5现在可以从一个公式导出，接着被用来通过单子函数的管道向codice_2填入值：
这种单子列表的一个应用是表示。codice_2可以持有一个算法中所有执行路径的结果，接着每一步骤压缩自身来忘记那一步导致了这个结果（有时这是同确定性、穷举算法的重要区别）。另一利益是检查可以嵌入到单子中；特定路径可以透明的在它们第一个失败点上被剪除，而不需要重写管道上的函数。
突出codice_2的第二种情况是复合多值函数。例如，一个数的codice_136次复数方根将产生codice_136个不同复数，但是如果另个codice_3方根接受了这些结果，最终复合出的codice_139的值应当同一于一次codice_139次方根的输出。codice_2完全自动化了这个问题的处置，压缩来自每一步骤的结果成一个平坦的、数学上正确的列表。
更多例子.
IO单子（Haskell）.
正如提及过的那样，纯粹的代码不应有不可管理的副作用，但是不妨碍程序“显式”的描述和管理各种作用。这个想法是Haskell的IO单子的中心，在这里一个类型codice_142的对象，可以被看作包含了程序外部的世界的当前状态，并计算类型codice_8的一个值。不计算值的计算，也就是过程，有着类型codice_144，它“计算”虚设值codice_145。在编程者bind一个codice_146值到一个函数的时候，这个函数基于世界的场景（来自用户的输入、文件等）做出决定，接着产生反映新的世界状态（程序输出）的一个单子值。
例如，Haskell有一些函数作用在宽广的文件系统之上，包括有检查一个文件存在的一个函数和删除一个文件的另一函数。二者的类型签名是：
<syntaxhighlight lang="haskell">
doesFileExist :: FilePath -> IO Bool
removeFile :: FilePath -> IO ()
</syntaxhighlight>
第一个函数关注一个给定文件是否真的存在，作为结果输出一个布尔值于codice_146单子之内。第二个函数在另一方面，只关心在文件系统上的起到作用，所以对于codice_146容器它们的输出为空。
codice_146不只限于文件I/O；它甚至允许用户I/O，还有指令式语法糖，可以模仿典型的Hello World程序：
<syntaxhighlight lang="haskell">
main :: IO ()
main = do
</syntaxhighlight>
不加语法糖，代码可以转写为如下单子管道（在Haskell中codice_150是codice_5的一种变体，用在只有单子作用是紧要的而底层结果可以丢弃的时候）：
<syntaxhighlight lang="haskell">
main :: IO ()
main =
</syntaxhighlight>
Writer单子（JavaScript）.
另一个常见的情况是保存日志文件或以其他方式报告程序的进度。有时，编程者想要记录更特殊的技术数据用于以后的性能分析或调试。Writer单子可以通过生成逐步积累的辅助输出来处理这些任务。
为了展示单子模式不局限于主要的函数式语言，这个例子用JavaScript实现了codice_152单子。首先，数组（具有嵌套的尾部）允许构造codice_152类型为链表。底层的输出值将位于这个数组的位置0，而位置1将隐蔽的持有连成一链的一些辅助注释：
<syntaxhighlight lang="Javascript">const writer = [];</syntaxhighlight>
定义codice_4是非常简单的：
<syntaxhighlight lang="Javascript">const unit = value => [];</syntaxhighlight>
定义输出具有调试注释的codice_152对象的简单函数只需要codice_4：
<syntaxhighlight lang="Javascript">
const squared = x => * x, [`${x} was squared.`];
const halved = x => / 2, [`${x} was halved.`];
</syntaxhighlight>
真正的单子仍需要codice_5，但是对于codice_152，这简单的相当于将函数的输出附加至单子的链表： 
<syntaxhighlight lang="Javascript">
const bind = (writer, transform) => {
</syntaxhighlight>
样例函数现在可以使用codice_5链接起来，但是定义单子复合的一个版本（这里叫做codice_160）允许更加简洁的应用这些函数：
<syntaxhighlight lang="Javascript">
const pipelog = (writer, ...transforms) =>
</syntaxhighlight>
最终结果是在逐步计算和为以后审查而记录之间的清晰的关注分离： 
<syntaxhighlight lang="Javascript">
pipelog(unit(4), squared, halved);
// 结果的writer对象 = ['4 was squared.', '16 was halved.']
</syntaxhighlight>
外部链接.
HaskellWiki参考：
教程：
个案：