为什么 monad 转换器与堆叠 monad 不同?

Why are monad transformers different to stacking monads?

在许多情况下,我不清楚将两个 monad 与转换器组合而不是使用两个单独的 monad 可以获得什么。显然,使用两个单独的 monad 很麻烦,并且可以在 do 表示法中包含 do 表示法,但是否存在表达力不够的情况?

一个案例似乎是 List 上的 StateT:组合 monad 不会让你得到正确的类型,如果你确实通过一堆 monad 获得正确的类型,比如 Bar (其中 Bar a = (Reader r (List (Writer w (Identity a))),它没有做正确的事情。

但我希望更全面地了解 monad 转换器究竟为 table 带来了什么,何时需要,何时不需要,以及为什么。

为了让这个问题更有针对性:

  1. 没有相应转换器的 monad 的实际示例是什么(这将有助于说明转换器可以做什么而只是堆叠 monad 不能)。
  2. StateT 和 ContT 是否是唯一提供不等同于它们与 m 组合的类型的转换器,对于底层 monad m(无论它们的组合顺序如何)。

(我对关于不同库选择的特定实现细节不感兴趣,而是一般的(可能 Haskell 独立的)问题,即添加什么 monad transformers/morphisms 作为替代通过堆叠一堆单子类型构造函数来组合效果。)

(为了提供一点背景信息,我是一名语言学家,正在做一个项目来丰富 Montague 语法 - 简单地键入 lambda 演算,将词义组合成句子 - 使用 monad 转换器堆栈。理解它真的很有帮助变形金刚是否真的对我有用。)

谢谢,

鲁本

What is an actual example of a monad with no corresponding transformer (this would help illustrate what transformers can do that just stacking monads can't).

IOST 是这里的典型示例。

Are StateT and ContT the only transformers that give a type not equivalent to the composition of them with m, for an underlying monad m (regardless of which order they're composed.)

不,ListT m a 不是(同构于)[m a]:

newtype ListT m a =
  ListT { unListT :: m (Maybe (a, ListT m a)) }

为了回答您关于 Writer w (Maybe a)MaybeT (Writer w) a 之间的区别的问题,让我们先来看看定义:

newtype WriterT w m a = WriterT { runWriterT :: m (a, w) }
type Writer w = WriterT w Identity

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

使用 ~~ 表示 "structurally similar to" 我们有:

Writer w (Maybe a)  == WriterT w Identity (Maybe a)
                    ~~ Identity (Maybe a, w)
                    ~~ (Maybe a, w)

MaybeT (Writer w) a ~~ (Writer w) (Maybe a)
                    == Writer w (Maybe a)
                    ... same derivation as above ...
                    ~~ (Maybe a, w)

所以从某种意义上说你是对的——在结构上 Writer w (Maybe a)MaybeT (Writer w) a 是相同的 - 两者本质上只是一对 Maybe 值和 w.

区别在于我们如何将它们视为一元值。 return>>= class 函数做的事情非常不同,具体取决于 它们属于哪个 monad。

让我们考虑一下 (Just 3, []::[String]) 对。使用协会 我们在上面推导出了这对在两个单子中的表达方式:

three_W :: Writer String (Maybe Int)
three_W = return (Just 3)

three_M :: MaybeT (Writer String) Int
three_M = return 3

下面是我们如何构造一对 (Nothing, []):

nutin_W :: Writer String (Maybe Int)
nutin_W = return Nothing

nutin_M :: MaybeT (Writer String) Int
nutin_M = MaybeT (return Nothing)   -- could also use mzero

现在考虑成对的这个函数:

add1 :: (Maybe Int, String) -> (Maybe Int, String)
add1 (Nothing, w) = (Nothing w)
add1 (Just x, w)  = (Just (x+1), w)

让我们看看如何在两个不同的 monad 中实现它:

add1_W :: Writer String (Maybe Int) -> Writer String (Maybe Int)
add1_W e = do x <- e
             case x of
               Nothing -> return Nothing
               Just y  -> return (Just (y+1))

add1_M :: MaybeT (Writer String) Int -> MaybeT (Writer String) Int
add1_M e = do x <- e; return (e+1)
  -- also could use: fmap (+1) e

通常你会发现 MaybeT monad 中的代码更简洁。

此外,从语义上讲,这两个 monad 非常不同...

MaybeT (Writer w) a 是一个可以失败的 Writer-action,失败是 自动为您处理。 Writer w (Maybe a) 只是一个作家 return 可能是一个动作。如果那个 Maybe 值没有什么特别的发生 结果是什么都没有。这在 add1_W 函数中得到了例证,其中 我们必须对 x.

进行案例分析

另一个更喜欢 MaybeT 方法的原因是我们可以编写代码 这对任何 monad 堆栈都是通用的。例如,函数:

square x = do tell ("computing the square of " ++ show x)
              return (x*x)

可以在任何具有 Writer String 的 monad 堆栈中不变地使用,例如:

WriterT String IO
ReaderT (WriterT String Maybe)
MaybeT (Writer String)
StateT (WriterT String (ReaderT Char IO))
...

但是 square 的 return 值不会针对 Writer String (Maybe Int) 进行类型检查,因为 square 不会 return 一个 Maybe

当您在 Writer String (Maybe Int) 中编写代码时,您的代码明确地揭示了 monad 的结构使其不那么通用。 add1_W的这个定义:

add1_W e = do x <- e 
              return $ do 
                y <- x 
                return $ y + 1

仅适用于两层 monad 堆栈,而像 square 这样的函数 在更通用的环境中工作。