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

Nir*_*n M 11 monads haskell composition monad-transformers

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

newtype Compose f g a = Compose { getCompose :: f (g a) }
Run Code Online (Sandbox Code Playgroud)

我写了以下定义

函子:

fmap f (Compose fga) = Compose $ (fmap . fmap) f fga
Run Code Online (Sandbox Code Playgroud)

适用范围:

(Compose f) <*> (Compose a) = Compose $ (<*>) <$> f <*> a
Run Code Online (Sandbox Code Playgroud)

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

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

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

单子:

(>>=) :: Compose f g a -> (a -> Compose f g b) -> Compose f g b
(Compose fga) >>= h = (fmap.fmap) h fga
Run Code Online (Sandbox Code Playgroud)

当然从GHC得到了这个错误

预期类型:撰写fgb

实际类型:f(g(合成fgb))

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

我试着阅读从其他堆栈溢出的问题,如答案这个,但所有的答案是更多的理论或一些数学。我仍然不知道为什么Monads不作曲。有人可以不使用数学就向我解释吗?

小智 19

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

join :: Monad m => m (m a) -> m a
Run Code Online (Sandbox Code Playgroud)

join>>=定义的替代方法Monad,并且推理起来稍微容易一些。(但是,现在你有一个运动要做到:展示如何实现>>=join,以及如何实现join>>=!)

让我们尝试进行join操作Composed f g,看看出了什么问题。我们的输入实质上是type的值f (g (f (g a))),并且我们想产生type的值f (g a)。我们也知道我们分别拥有joinfor fg,因此,如果我们可以获取type的值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
Run Code Online (Sandbox Code Playgroud)

注意:distribute为了确保join我们到达这里的合法性,我们需要满足一些法律。


好的,这表明如果我们有分配律, 我们如何组成两个单子distribute :: (Monad f, Monad g) => g (f a) -> f (g a)。现在,它可能是每对单子有分配律是真实的。也许我们只需要认真思考如何写下一个?

不幸的是,有一些没有分配律的单子。因此,我们可以通过生成两个绝对不能将a g (f a)变为a的单子来回答您的原始问题f (g a)。这两个单子将见证一个事实,即单子通常不组成。

我主张g = IO并且f = Maybe没有分配法

-- Impossible!
distribute :: IO (Maybe a) -> Maybe (IO a)
Run Code Online (Sandbox Code Playgroud)

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


综上所述:

  • 只有有分配律的情况下,Monads才可以作曲g (f a) -> f (g a)
  • 有些单子没有这样的分配法则。
  • 某些单子可以互相组成,但不是对单子都可以组成。

  • @RobinZigmond`distribute(getLine &gt;&gt; =(\ s-&gt;如果长度s&gt; 2则返回(Just s)否则返回Nothing))::也许(IO String)` 因此,这是一个**已经知道**用户**将键入**的值。如果它是“ Just act”,我们可以执行该“ act”,它将神奇地产生一个保证输入至少3个字符的用户!那不是不安全的I / O,那纯粹是魔术。//无论如何,这就是我的理解方式。 (2认同)