为什么单子不根据组成关闭?

时间:2019-03-07 12:53:44

标签: haskell monads composition monad-transformers

当我从Haskell Book学习Composing Types一章时,我被赋予编写以下类型的Functor和Applicative实例的任务。

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

我写了以下定义

Functor:

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

适用:

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

我了解到,组成两个Functor或Applicatives分别会使Functor和Applicative。

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

到目前为止,我尝试编写如下的bind函数:

Monad:

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

当然从GHC得到了这个错误

  

预期类型:撰写f g b

     

实际类型:f(g(组成f g b))

如果我能以某种方式去除最外面的f g,那么构图就给我们一个单子? (尽管如此,我仍然不知道该如何剥离)

我尝试阅读其他类似this的Stack Overflow问题的答案,但是所有答案都是理论上的或数学上的。我仍然不知道为什么Monads不作曲。有人可以不用我解释一下我吗?

2 个答案:

答案 0 :(得分:18)

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

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

join>>=的另一种定义Monad的方法,并且推理起来稍微容易一些。 (但是现在您需要做一个练习:展示如何从>>=实现join,以及如何从join实现>>=!)

让我们尝试对join进行Composed f g操作,看看出了什么问题。我们的输入实质上是类型f (g (f (g a)))的值,并且我们想产生类型f (g a)的值。我们也知道我们分别为joinf拥有g,因此,如果我们可以得到类型为f (f (g (g a)))的值,那么可以用{{1} },以获得我们想要的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 :: g (f a) -> f (g a)变成g (f a)的单子来回答您的原始问题。这两个单子将见证一个事实,即单子通常不组成。

我声称f (g a)g = IO没有分配律

f = Maybe

让我们考虑一下为什么这样的事情应该是不可能的。此功能的输入是一个IO动作,它进入现实世界并最终产生-- Impossible! distribute :: IO (Maybe a) -> Maybe (IO a) Nothing。该函数的输出是Just xNothing的IO操作,该操作在运行时最终会产生Just。为了产生x,我们将不得不窥视未来并预测Maybe (IO a)行动将要做什么!


总结:

  • 仅在有分配律IO (Maybe a)的情况下,Monad才能构成。
  • 有些单子没有这样的分配法则。
  • 一些单子可以相互构成,但每对单子不能构成。

答案 1 :(得分:1)