为什么单子在组合下不封闭?
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)
类型的值。我们还知道 f
和 g
分别有 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 = IO
和 f = Maybe
没有分配律
-- Impossible!
distribute :: IO (Maybe a) -> Maybe (IO a)
让我们想想为什么这样的事情是不可能的。这个函数的输入是一个 IO 动作,它进入现实世界并最终产生 Nothing
或 Just x
。此函数的输出是 Nothing
或 Just
一个 IO 操作,当 运行 时,最终会产生 x
。要生成 Maybe (IO a)
,我们必须展望未来并预测 IO (Maybe a)
操作将要执行的操作!
总结:
- 如果存在分配律
g (f a) -> f (g a)
,则单子可以组合。 (但请参阅下面的附录)
- 有些单子没有这样的分配律。
- 一些 单子可以相互组合,但不是每对 单子都可以组合。
附录:“如果”,但是“只有当”呢?如果F
、G
、FG
这三个都是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.
当我学习 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 问题的答案,例如
我认为通过查看 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)
类型的值。我们还知道 f
和 g
分别有 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 = IO
和 f = Maybe
没有分配律
-- Impossible!
distribute :: IO (Maybe a) -> Maybe (IO a)
让我们想想为什么这样的事情是不可能的。这个函数的输入是一个 IO 动作,它进入现实世界并最终产生 Nothing
或 Just x
。此函数的输出是 Nothing
或 Just
一个 IO 操作,当 运行 时,最终会产生 x
。要生成 Maybe (IO a)
,我们必须展望未来并预测 IO (Maybe a)
操作将要执行的操作!
总结:
- 如果存在分配律
g (f a) -> f (g a)
,则单子可以组合。 (但请参阅下面的附录) - 有些单子没有这样的分配律。
- 一些 单子可以相互组合,但不是每对 单子都可以组合。
附录:“如果”,但是“只有当”呢?如果F
、G
、FG
这三个都是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.