直观上函子和单子有什么区别

yix*_*ing 2 monads haskell functional-programming functor

我已经学习了函子和单子的定义,但除了定义之外我仍然无法弄清楚它们之间的区别。我读过这个问题的一些答案,在Functor 和 Monad 之间有什么区别?一条评论说

函子采用纯函数(和函子值),而单子采用 Kleisli 箭头,即返回单子(和单子值)的函数。因此,您可以链接两个 monad,第二个 monad 可以依赖于前一个 monad 的结果。你不能用函子来做到这一点。

这个评论很有趣,让我了解了它们的区别。但我还有一些问题。

  1. 为什么函子不能使用前一个函子的结果?因为fmap :: (a -> b) -> f a -> f b,当我fmap使用纯函数进行柯里化时,我可以获得一个f a -> f b函数,f b取决于,结果f a是否意味着函子内的数据?
  2. 在范畴论中,我可以理解注释,因为我无法获取范畴论中的元素,但在Haskell中,我发现我可以使用函子的结果,因为Haskell可以记住数据构造函数,Haskell是否会阻止我理解此注释?我应该用纯范畴论来理解这一点吗?

Ben*_*Ben 5

使用 时fmap :: Functor f => (a -> b) -> f a -> f b,参数函数永远不能依赖于函子本身的任何结构f;当它传递的都是a值时,它怎么可能与 无关f?当您使用fmapgetf a -> f b函数时,负责构建最终f b值的代码是其本身的代码,而不是被映射函数fmap的代码。a -> b被映射的函数甚至可能根本不会被调用(例如,如果您使用函Maybe子并且f a值为Nothing,则其中没有a值可以fmap传递给函数a -> b,并且永远不会被调用)。

但和fmap是完全多态的,这意味着 的代码不知道这些类型是什么,也不能对它们进行任何假设。因此必须以仅依赖于;添加的函子结构的方式构建最终值。的代码无法查看任何值并决定要做什么。事实上,合法函数唯一能做的就是返回一个与输入值结构完全相同的值,只是内部的任何 s 被函数返回的值替换。这就是为什么您可以使用任何数据类型;至多有一种方法可以使任何给定的数据类型成为 a ,并且编译器可以为您完成它。abfmapfmapf bffmapafmapf aaba -> bderiving FunctorFunctor

但是 monad=<<函数1有这样的类型:Monad m => (a -> m b) -> m a -> m b。这里映射函数的类型为a -> m b。这意味着最后m b返回的最终结构并不完全由代码决定=<<(至少不一定)。该a -> m b函数知道正在使用什么 monad并在其值中返回一些 monad 结构m b,而不仅仅是原始b. 映射函数仍然不接收任何单子结构,因此它的中间m b结果不能依赖于此;最终m b值对原始单子结构的任何依赖m a都必须由代码确定=<<(同样,不能根据a值本身做出任何决定)。但是映射函数(如果被调用)确实会贡献一些一元结构,并且这可能取决于a值(因为映射函数不必在 中完全多态a;它可以检查a值并根据它们做出决策)。

这意味着最终输出的任何m b部分都可能依赖于输入的任何部分(如果我们不知道映射了什么函数,或者函数=<<内部如何工作)。这与函子的情况完全不同,即使不知道任何关于特定函子或映射函数的信息,我们也知道最终的输出总是看起来像“输入的副本,其中as 被 s 替换b”(特别是它b替换了每个a由映射函数确定)。


可能值得注意的是:我在这里所说的几乎所有内容都是这些类型含义的直接结果:

Functor f => (a -> b) -> f a -> f b
Monad m => (a -> m a) -> m a -> m b
Run Code Online (Sandbox Code Playgroud)

这些不是您必须记住的函子和单子的特殊属性,它只是具有这些类型的操作的结果。(函子和单子定律表达了类型所不表达的东西,但我不需要调用它们来解释这种差异)

一旦您真正深入习惯了以 Haskell 方式思考类型,您只需查看类型就可以自己弄清楚这一点。我当然没有;我学习的时候有人给我解释过(十几次),从那以后我就记住了。但现在我可以弄清楚我以前从未见过的新 API 的类似内容,而无需任何教程或解释。


1我使用反向=<<运算符而不是传统运算符,仅仅是因为它与/运算符>>=更一致。fmap<$>