从实际角度来看,你可以将OCaml和Haskell中的"函数子"视为不相关的。正如你所说,在Haskell中,任何允许你在其上映射函数的类型都是函数子。而在OCaml中,函数子是一个由另一个模块参数化的模块。
函数式编程中什么是函数子?很好地描述了两种语言中的函数子以及它们之间的差异。
但是,正如名称所示,这两个看似不相关的概念实际上存在联系!两种语言中的函数子都只是范畴论中的一个概念的实现。
范畴论研究的是范畴,它们只是具有“态射”(或称“箭头”)的任意对象集合。范畴的概念非常抽象,因此“对象”和“态射”可以是任何带有一些限制的东西-每个对象都必须有一个恒等态射,并且态射必须能够组合。
最明显的范畴示例是集合和函数的范畴:集合是对象,集合之间的函数是态射。显然,每个集合都有一个恒等函数,并且函数可以组合。通过查看像Haskell或OCaml这样的函数式编程语言,可以形成非常相似的范畴:具体类型(例如带有
*
种类的类型)是对象,而Haskell/OCaml函数是它们之间的态射。
在范畴论中,函子是范畴之间的变换。它就像范畴之间的函数。当我们查看Haskell类型的范畴时,函子基本上是一种类型级别的函数:它将类型映射到其他内容。我们关心的特定类型的函子将类型映射到其他类型。这方面的完美示例是
Maybe
:
Maybe
将
Int
映射到
Maybe Int
,将
String
映射到
Maybe String
等等。它为
每个可能的Haskell类型提供了映射。
Functors有一个额外的要求 - 它们必须将范畴的态射以及对象映射。特别地,如果我们有一个态射A→B,并且我们的functor将A映射到A'和B映射到B',它必须将态射A→B映射到某个态射A'→B'。作为一个具体的例子,假设我们有类型Int和String。有很多Haskell函数Int→String。对于Maybe成为一个合适的functor,它必须有一个函数Maybe Int→Maybe String,其中包括每一个这样的函数。
令人高兴的是,这正是fmap函数所做的 - 它映射函数。对于Maybe,它具有类型(a→b)→Maybe a→Maybe b; 我们可以添加一些括号来得到:(a→b)→(Maybe a→Maybe b)。这个类型签名告诉我们,对于任何我们拥有的普通函数,我们也有相应的函数在Maybe上。
因此,一个函子是类型之间的映射,同时也保留它们之间的函数。函数fmap本质上只是对函子的第二个限制的证明。这使得很容易看出Haskell Functor类只是数学概念的一个特定版本。
那么OCaml呢?在OCaml中,functor不是一种类型-它是一个模块。特别地,它是一个参数化模块:一个接受另一个模块作为参数的模块。我们已经可以看到一些相似之处:在Haskell中,Functor就像类型级函数;在OCaml中,functor就像模块级函数。因此,它实际上是相同的数学思想;然而,它不是用于像Haskell中那样的类型,而是用于模块。
关于OCaml的functor与范畴论functor的更多细节,请参考CS网站上的内容:SML中functor与范畴论functor之间的关系是什么?。该问题讨论的是SML而非OCaml per se,但我的理解是,OCaml的模块系统与SML非常密切相关。
总之:Haskell和OCaml中的functor是两种根本不同的结构,它们都恰好是同一个非常抽象的数学概念的具体化。我认为这很棒 :).