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