从实际的角度来看,您可以认为 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)。这个类型签名告诉我们的是,对于我们拥有的任何普通函数,我们在Maybes 上也有一个对应的函数。
所以函子是类型之间的映射,它也保留了它们之间的函数。该fmap函数本质上只是对函子的第二个限制的证明。这使得很容易看出 HaskellFunctor类只是数学概念的一个特定版本。
那么OCaml呢?在 OCaml 中,仿函数不是类型——它是一个模块。特别是,它是一个参数化模块:一个将另一个模块作为参数的模块。我们已经可以看到一些相似之处:在 Haskell 中,aFunctor就像一个类型级函数;在 OCaml 中,仿函数就像一个模块级函数。所以真的,这是同一个数学概念。但是,它不像在 Haskell中那样用于类型,而是用于模块。
在 CS 网站上有更多关于 OCaml 函子如何与类别理论函子相关的详细信息:SML 中的函子与类别理论之间的关系是什么?. 问题是关于 SML 而不是 OCaml本身,但我的理解是 OCaml 的模块系统与 SML 的模块系统非常密切相关。
总之:Haskell 和 OCaml 中的函子是两种根本不同的结构,它们恰好都是同一个非常抽象的数学思想的具体化。我认为这很整洁:)。