在 Haskell 中编写嵌套的 Monad

Compose nested Monads in Haskell

有没有办法为嵌套的 monad 实现绑定?我要的是下面的签名:

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

看起来这应该是一项微不足道的任务,但我不知何故就是无法理解它。在我的程序中,我将这种模式用于几种不同的 monad 组合,并且对于每种组合,我都可以实现它。但是对于一般情况,我就是看不懂。

编辑:似乎在一般情况下是不可能的。但在某些特殊情况下肯定是可以的。例如。如果内部 Monad 是 Maybe。因为我想使用的所有 Monad 都是可能的,所以有额外的限制对我来说似乎很好。所以我稍微改变一下问题:

我需要对 n 进行哪些额外约束才能实现以下条件?

(>>>=) :: (Monad m, Monad n, ?? n) => m (n a) -> (a -> m (n b)) -> m (n b)

扩展评论:如 linked 所示,有必要具有一些功能 n (m a) -> m (n a) 甚至有机会使组合成为 monad。

如果你的inner monad是一个Traversable,那么sequence提供了这样一个函数,下面就会有正确的type:

(>>>=) :: (Monad m, Monad n, Traversable n) => m (n a) -> (a -> m (n b)) -> m (n b)
m >>>= k = do
    a <- m
    b <- sequence (map k a)
    return (join b)

几个著名的转换器实际上是简单的新类型包装器,对等同于此的东西进行包装(尽管大多数情况下使用模式匹配来定义事物,而不是字面上使用内部 monad 的 MonadTraversable 实例) :

  • MaybeT 基于 Maybe
  • ExceptT 基于 Either
  • WriterT 基于 (,)(,) 通常没有定义其 Monad 实例, WriterT 正在使用错误的元组顺序来使用它,如果它有的话 - 但在精神上它可能有)。
  • ListT 基于 []。哦,哎呀...

最后一个实际上是臭名昭著的,因为 不是 是单子,除非提升的单子是 "commutative" - 否则,根据单子法则应该相等的表达式可以给出不同顺序的效果。我的直觉是,这主要来自能够包含多个值的列表,这与其他可靠工作的示例不同。

所以,虽然上面的定义类型是正确的,它仍然可以打破单子法则。

另外一个事后的想法是,另一个转换器就是这样一个嵌套的 monad,但是以完全不同的方式:ReaderT,基于使用 (->) 作为 outer单子。