首页 文章

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

提问于
浏览
20

在许多情况下,我不清楚将两个monad与变压器组合而不是使用两个单独的monad可以获得什么 . 显然,使用两个独立的monad是一件麻烦事,并且可能涉及到符号内部的符号,但是有些情况下它只是表达不够吗?

一个案例似乎是列表上的StateT:组合monads不能得到正确的类型,如果你通过像Bar这样的monad栈获得正确的类型(其中Bar a =(Reader r(List(Writer w(Identity) a))),它没有做正确的事情 .

但是我想更准确地理解monad变压器带来什么,当它们是否必要时,以及为什么 .

为了使这个问题更加集中:

  • 什么是没有相应变换器的monad的实际示例(这将有助于说明变换器可以做什么,只是堆叠monad不能) .

  • StateT和ContT是唯一的变换器,它们给出的类型与m的组成不等同于m,对于底层monad m(无论它们是由哪个顺序组成的 . )

(关于库的不同选择,我对特定的实现细节不感兴趣,而是对monad变换器/态射正在添加的一般问题(可能是Haskell独立)的问题,作为通过堆叠一堆monadic类型构造函数来组合效果的替代方法 . )

(为了给出一点背景,我是一个语言学家,正在做一个丰富蒙塔古语法的项目 - 简单地输入lambda演算,用于将单词意义组成句子 - 用monad变换器堆栈 . 理解变换器是否真的在做真的很有帮助对我有用的任何东西 . )

谢谢,

鲁本

2 回答

  • 21

    要回答关于 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 .

    不同之处在于我们如何将它们视为monadic值 . return>>= 类函数的功能完全不同,具体取决于它们所属的monad .

    让我们考虑一对 (Just 3, []::[String]) . 使用我们在上面得到的关联是如何在两个monad中表示该对:

    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中的代码更简洁 .

    而且,在语义上两个单子是非常不同的......

    MaybeT (Writer w) a 是一个可能失败的Writer操作,并且会自动为您处理失败 . Writer w (Maybe a) 只是一个返回Maybe的Writer动作 . 如果那个Maybe值变成Nothing,那么什么都不会发生 . 这在 add1_W 函数中有所例证,我们必须在 x 上执行案例分析 .

    更喜欢 MaybeT 方法的另一个原因是我们可以编写在任何monad堆栈上都是通用的代码 . 例如,功能:

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

    可以在任何具有Writer字符串的monad堆栈中保持不变,例如:

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

    但是 square 的返回值不会对 Writer String (Maybe Int) 进行类型检查,因为 square 不会返回 Maybe .

    当您在 Writer String (Maybe Int) 中编码时,您的代码显式地显示了monad的结构,使其不那么通用 . add1_W 的定义:

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

    仅适用于双层monad堆栈,而像 square 这样的函数在更常规的设置中工作 .

  • 6

    什么是没有相应变压器的monad的实际示例(这将有助于说明变压器可以做什么,只是堆叠monad不能) .

    IOST 是这里的规范示例 .

    对于基础monad m(无论它们是由哪种顺序组成),StateT和ContT是唯一一种类型不等于m的类型的变换器 .

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

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

相关问题