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

问题描述 投票:20回答:2

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

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

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

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

  1. 什么是没有相应变压器的monad的实际示例(这将有助于说明变压器可以做什么只是堆叠monad不能)。
  2. StateT和ContT是唯一的变换器,它们给出的类型与m的组成不等同于m,对于底层monad m(无论它们是由哪个顺序组成的。)

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

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

谢谢,

鲁本

haskell monads monad-transformers category-theory
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) }

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

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 w (Maybe a)只是一个回复了一个可能的作家行动。如果那个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是这里的典型例子。

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

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

newtype ListT m a =
  ListT { unListT :: m (Maybe (a, ListT m a)) }
© www.soinside.com 2019 - 2024. All rights reserved.