为什么单子在组合下不封闭?

Why are monads not closed under composition?

当我学习 Haskell 书中的 Composing Types 章节时,我被分配了为以下类型编写 Functor 和 Applicative 实例的任务。

newtype Compose f g a = Compose { getCompose :: f (g a) }

我写了下面的定义

仿函数:

fmap f (Compose fga) = Compose $ (fmap . fmap) f fga

适用的:

(Compose f) <*> (Compose a) = Compose $ (<*>) <$> f <*> a

我了解到组合两个 Functor 或 Applicative 分别给出 Functor 和 Applicative。

作者还解释了不可能以相同的方式组合两个 Monad。所以我们使用 Monad Transformers。我只是不想阅读 Monad 变形金刚,除非我清楚为什么 Monad 不构成。

到目前为止,我尝试这样编写 bind 函数:

单子:

(>>=) :: Compose f g a -> (a -> Compose f g b) -> Compose f g b
(Compose fga) >>= h = (fmap.fmap) h fga

当然从 GHC 得到了这个错误

Expected type: Compose f g b

Actual type: f (g (Compose f g b))

如果我能以某种方式去除最外层的 f g ,组合就给了我们一个 monad 对吗? (虽然我仍然不知道如何去除它)

我尝试阅读其他 Stack Overflow 问题的答案,例如 ,但所有答案都比较理论或一些数学。我仍然没有了解为什么 Monad 不组合。有人可以不使用数学来解释我吗?

我认为通过查看 join 运算符最容易理解:

join :: Monad m => m (m a) -> m a

join>>= 的替代方法,用于定义 Monad,并且更容易推理。 (但现在你有一个练习要做:展示如何从 join 实现 >>=,以及如何从 >>= 实现 join!)

让我们尝试对 Composed f g 进行 join 操作,看看哪里出了问题。我们的输入本质上是一个 f (g (f (g a))) 类型的值,我们想要生成一个 f (g a) 类型的值。我们还知道 fg 分别有 join,所以如果我们可以得到类型 f (f (g (g a))) 的值,那么我们可以用 [=30= 命中它] 得到我们想要的f (g a)

现在,f (f (g (g a))) f (g (f (g a))) 相去不远。我们真正需要的只是一个像这样的函数:distribute :: g (f a) -> f (g a)。然后我们可以像这样实现 join

join = Compose . fmap join . join . fmap (distribute . fmap getCompose) . getCompose

注意:我们希望 distribute 满足一些法律,以确保我们到达这里的 join 是合法的。


好的,这说明了如果我们有分配律 distribute :: (Monad f, Monad g) => g (f a) -> f (g a),我们如何组合两个单子。现在,可能每对单子都有分配律。也许我们真的需要好好想想怎么写下来?

不幸的是 对没有分配律的单子。因此,我们可以通过生成两个绝对 没有 g (f a) 转换为 f (g a) 的单子来回答您最初的问题。这两个 monad 将证明 monad 通常不会组合。

我声称 g = IOf = Maybe 没有分配律

-- Impossible!
distribute :: IO (Maybe a) -> Maybe (IO a)

让我们想想为什么这样的事情是不可能的。这个函数的输入是一个 IO 动作,它进入现实世界并最终产生 NothingJust x。此函数的输出是 NothingJust 一个 IO 操作,当 运行 时,最终会产生 x。要生成 Maybe (IO a),我们必须展望未来并预测 IO (Maybe a) 操作将要执行的操作!


总结:

  • 如果存在分配律 g (f a) -> f (g a),则单子可以组合。 (但请参阅下面的附录)
  • 有些单子没有这样的分配律。
  • 一些 单子可以相互组合,但不是每对 单子都可以组合。

附录:“如果”,但是“只有当”呢?如果FGFG这三个都是monad,那么可以构造一个自然变换δ : ∀X. GFX -> FGX作为GFη_X : GFX -> GFGX后跟[=​​的组合56=] 然后 μ_X : FGFGX -> FGX。在 Haskellese 中(为了清楚起见,使用显式类型应用程序),那将是

delta :: forall f g x. (Monad f, Monad g, Monad (Compose f g))
      => g (f x) -> f (g x)
delta = join' . pure @f . fmap @g (fmap @f (pure @g)) 
  where
    -- join for (f . g), via the `Monad (Compose f g)` instance
    join' :: f (g (f (g x))) -> f (g x)
    join' = getCompose . join @(Compose f g) . fmap Compose . Compose

所以如果组合FG是一个单子,那么你可以得到一个具有正确形状的自然变换成为分配律。但是,为了确保您的分配律满足上面模糊提到的正确属性,还有一些额外的限制。一如既往,the n-Category Cafe has the gory details.