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

时间:2016-08-02 00:45:05

标签: haskell monads monad-transformers category-theory

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

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

但我想更准确地理解monad变形金刚带来什么,当它们是否必要时,以及为什么。

让这个问题更加集中:

  1. 没有相应变压器的monad的实际示例是什么(这有助于说明变压器可以做什么只是堆叠monad不能)。
  2. StateT和ContT是唯一的变换器,它们给出的类型与m的构成不等同于m,对于底层monad m(无论它们是由哪个顺序组成的。)
  3. (关于库的不同选择,我对特定的实现细节不感兴趣,而是对monad变换器/态射正在添加的一般问题(可能是Haskell独立)的问题,作为通过堆叠一堆来组合效果的替代方案monadic型构造函数。)

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

    谢谢,

    流便

2 个答案:

答案 0 :(得分: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) }

使用~~表示“结构上与我们相似”,我们有:

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-action,失败的是 自动为您处理。 Writer w (Maybe a)只是一个作家 返回Maybe的动作。如果那个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的返回值未对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这样的函数 在更一般的环境中工作。

答案 1 :(得分:6)

  

没有相应变压器的monad的实际示例是什么(这有助于说明变压器可以做什么,只需堆叠monad就可以了。)

IOST是此处的规范示例。

  

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

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

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