【问题标题】:Monad transformer Inner structural orderMonad 变换器内部结构顺序
【发布时间】:2021-12-20 04:23:14
【问题描述】:

我一直想知道为什么 Monad Transformers 有 m(Maybe(m(Maybe a)) 结构顺序,为什么没有 Maybe(m(Maybe( m a)))。我已尝试实施第二个,但由于 Haskell 知识不足,我可能无法完成它。

在所有 monad 转换器中,我们总是有这样的结构吗? 如果是,那为什么? 如果没有,那么什么时候从另一个中选择一个?

newtype MaybeOT m a = MaybeOT {runMaybeOT :: Maybe (m  a) }
instance (Monad m) => Monad (MaybeOT m) where
return = pure
(MaybeOT mma) >>= f = MaybeOT $ 
                        case mma of
                            Nothing -> Nothing
                            (Just ma) -> inner ma where
                                         inner ma = do
                                            a <- ma
                                            undefined

【问题讨论】:

  • 您的意思是where inner ma = ... 而不是let
  • 我在某个时候为此实施了an Applicative instance,但甚至不确定它是否符合法律。
  • 主要的问题是,归根结底,您必须对m a 类型执行bind/&gt;&gt;=。然而,您得到了一个返回类型MaybeOT (m b) 的函数。当然,必须为m a 上的bind 提供一个返回m b 的函数,无论b。您可以从MaybeOT (m b) 获取Maybe (m a)。但接下来呢?外部 monad 的信息就是你所拥有的,它是实现 monadic 组合(转换器)所需的信息——但在这一步中你需要把它扔掉。
  • 相反,对于MaybeTMaybe 是内部单子。你绑定一次m (Maybe a)。在这里,您有机会检查 Maybe 的内部 Nothing。在这种情况下,pure 可以帮助您构建外部 monad。此外,您获得的函数返回MaybeT m b。您可以从中获得m (Maybe b) - 这正是您在m (Maybe a) 上成功bind 所需要的。因此,它运行良好。您已经使用了有关 Maybe 的信息以及它在实现中的确切含义,而不必将其丢弃。
  • 注意到MaybeOT 面临的主要问题是您在需要m (Maybe a) 的地方遇到了Maybe (m a)This is also the primary problem you face when implementing bind for arbitrary nested monads。因此,需要单子变压器。有趣的是,traversable monads 能够交换外部和内部上下文 - 从而允许它们绕过所有这些。

标签: haskell monads monad-transformers


【解决方案1】:

您基本上已经发现了为什么 monad 转换器是 inwards 而不是 outwards(尼特:我很确定我在这里没有使用正确的术语,但你明白了我的意思是 - 随时在这里纠正我)。让 您了解的 monad 位于 数据位置(内部)而不是 容器位置 要容易得多(容易 == 更少的约束)强>(外)。直观地,您可以看到原因 - 毕竟,单子性质要求 容器 是相同的。你可以对数据做任何你想做的事情,但你不能在绑定过程中改变容器的类型。

当然,通过实际尝试实现荒谬更容易实现这一切。这是关于什么的。在为MaybeOT 实施&gt;&gt;=/bind 时,您将被困住的最后一步是-

m a >>= ??? . runMaybeOT . f

其中,mMonadm a :: m af :: a -&gt; MaybeOT m arunMaybeOT :: MaybeOT m a -&gt; Maybe (m a)(还有 ??? :: ???,呵呵)

现在,您必须获取容器类型为 m 的 monad 才能成功绑定到 m a。但是很可惜,您无法从Maybe (m a) 中获得m a!当它是Nothing 时会发生什么?您实现 monad 转换器的主要工具是知识,了解您正在为其实现转换器的特定 monad。而你的知识告诉你这是荒谬的。

相比之下,为MaybeT 实施&gt;&gt;= 的最后一步进展顺利。为了完整起见,这里是MaybeT-

newtype MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) }

在这里,您将绑定m (Maybe a) 类型。在绑定期间,您必须返回类型m b,无论b。给你一个函数f,类型为a -&gt; MaybeT m b,你可以很容易地从MaybeT m b得到m (Maybe b)

啊哈!现在您可以看到解决方案。您获得的函数返回 与您绑定的相同的外部monad。这正是您所需要的。在 MaybeOT 的情况下,您被困在 monad m 上的绑定,其函数不返回外部 monad m 的值!

这是关键的实现——你被赋予的函数必须能够给你一个具有相同外部 monad 的值。这就是为什么 MaybeT(以及其他转换器)将未知的 monad 保持在外部的原因 - 因为当您为 MaybeT 实现 &gt;&gt;= 时,您知道构建该外部 monad 需要绑定函数。

请注意,您在此处遇到的问题与您在实施一元组合时将面临的问题完全相同,这很有帮助,至少在直觉上是这样。也就是说,&gt;&gt;= 用于嵌套单子。 Monads don't compose!这就是你需要 monad 转换器的原因!

如果你分解这个问题,你会注意到如果你有一个函数可以交换 monad,从m (Maybe a) 得到Maybe (m a),反之亦然——一切都会好起来的. Monad 会组成,monad 转换器可以看起来像你喜欢的任何样子,事实上,monad 转换器基本上没有任何用途。这个细节被注意到in the answer linked above。你只需要-

swap :: (Monad m, Monad n) => m (n a) -> n (m a)

This exists。它们被称为可遍历的 monad,实际上,如果您只是将 Traversable 约束添加到 MonadMaybeOT 实现中,那么您就中奖了-

instance (Traversable m, Monad m) => Monad (MaybeOT m) where
    return = pure
    MaybeOT Nothing >>= _   = MaybeOT Nothing
    MaybeOT (Just ma) >>= f = MaybeOT $ sequence $ ma >>= sequence . runMaybeOT . f

认为这是符合法律规定的,但我必须检查一下。

在另一个说明中,完全有可能使MaybeOT 成为符合法律的应用程序。毕竟,您在实现Monad 时遇到的问题在这里根本不存在。应用程序组合。

instance Applicative f => Applicative (MaybeT f) where
    pure = MaybeT . pure . Just
    MaybeT f <*> MaybeT a = MaybeT $ liftA2 (<*>) f a

(或者,也许更简单)

instance Applicative f => Applicative (MaybeT f) where
    pure = MaybeT . pure . Just
    MaybeT f <*> MaybeT a = MaybeT $ (<*>) <$> f <*> a

据我所知,应该符合法律。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-12-12
    相关资源
    最近更新 更多