当我从Haskell Book学习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或Applicatives分别会使Functor和Applicative。
作者还解释说,不可能以相同的方式组成两个Monad。因此,我们使用Monad变形金刚。我只是不想阅读Monad变形金刚,除非我清楚为什么Monad不作曲。
到目前为止,我尝试编写如下的bind
函数:
(>>=) :: 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不作曲。有人可以不用我解释一下我吗?
答案 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)
的值。我们也知道我们分别为join
和f
拥有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 x
或Nothing
的IO操作,该操作在运行时最终会产生Just
。为了产生x
,我们将不得不窥视未来并预测Maybe (IO a)
行动将要做什么!
总结:
IO (Maybe a)
的情况下,Monad才能构成。答案 1 :(得分:1)