当我从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或Applicative分别给出了Functor和Applicative。
作者还解释说,不可能以同样的方式组成两个Monads。所以我们使用Monad变形金刚。我只是不想阅读Monad变形金刚,除非我清楚为什么Monads不写作。
到目前为止,我试图写这样的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
,那么这个构图给了我们一个monad吧? (我仍然无法弄清楚如何剥离它)
我尝试从其他Stack Overflow问题中读取答案,例如this,但所有答案都更理论化或者数学。我还没有意识到为什么莫纳德不会写作。没有使用数学,有人可以解释我吗?
我认为通过查看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)))
类型的值,那么我们可以用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)
我们如何组成两个monad。现在,每一对monad都有一个分配定律。也许我们只需要认真考虑如何写一个?
不幸的是,有一对没有分配法的monad。因此,我们可以通过生成两个绝对没有办法将g (f a)
变成f (g a)
的单子来回答你原来的问题。这两个单子将见证单子一般不构成的事实。
我声称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)
,Monads只能撰写。