在我学习的时候Composing Types
Haskell Book 的章节中,我被赋予了为以下类型编写 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 Transformers,除非我清楚为什么 Monads 不作曲。
到目前为止,我试图写
bind
像这样的功能:单子(monad):
(>>=) :: 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 问题的答案,例如 this ,但所有答案都更理论化或数学化。我仍然不明白为什么 Monads 不组成。有人可以在不使用数学的情况下解释我吗?
最佳答案
我认为这通过查看 join
最容易理解。运算符(operator):
join :: Monad m => m (m a) -> m a
join
是 >>=
的替代品用于定义 Monad
,并且更容易推理。 (但现在你要做一个练习:展示如何从 >>=
实现 join
,以及如何从 join
实现 >>=
!)让我们尝试制作一个
join
Composed f g
的操作看看出了什么问题。我们的输入本质上是 f (g (f (g a)))
类型的值, 我们想要产生一个类型为 f (g a)
的值.我们也知道我们有 join
对于 f
和 g
单独,所以如果我们能得到一个类型为 f (f (g (g a)))
的值,然后我们可以用 fmap join . join
获取 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)
,我们如何组成两个 monad。 .现在,每一对单子(monad)都有一个分配律,这可能是真的。也许我们只需要认真思考如何写下来?不幸的是,有成对的单子(monad)没有分配律。因此,我们可以通过生成两个绝对无法转换
g (f a)
的单子(monad)来回答您最初的问题。进入 f (g a)
.这两个 monad 将见证 monad 通常不组成的事实。我声称
g = IO
和 f = Maybe
没有分配规律-- Impossible!
distribute :: IO (Maybe a) -> Maybe (IO a)
让我们想想为什么这样的事情是不可能的。这个函数的输入是一个 IO Action ,它进入现实世界并最终产生 Nothing
或 Just x
.此函数的输出是 Nothing
, 或 Just
一个 IO 操作,在运行时最终会产生 x
.生产 Maybe (IO a)
,我们将不得不窥视 future 并预测IO (Maybe a)
行动是要做的!总之:
g (f a) -> f (g a)
. (但请参阅下面的附录)附录:“如果”,但“仅当”呢?如果
F
的所有三个, G
, 和 FG
是单子(monad),那么你可以构造一个自然变换δ : ∀X. GFX -> FGX
作为 GFη_X : GFX -> GFGX
的组成后跟 η_{GFGX} : GFGX -> FGFGX
然后由 μ_X : FGFGX -> FGX
.在 Haskelese 中(为了清楚起见,使用显式类型应用程序),那将是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
是一个单子(monad),那么你可以得到一个具有正确形状的自然变换,成为一个分配律。但是,为了确保您的分配律满足正确的属性,还有一些额外的限制,上面模糊地提到过。一如既往,the n-Category Cafe has the gory details .
关于haskell - 为什么单子(monad)在组合下不关闭?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55044292/