关于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
的组合。
区别在于我们如何将它们作为单子值处理。根据它们所属的单子不同,return
和>>=
类函数会执行非常不同的操作。
让我们考虑一下配对项(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)
现在考虑这个针对一对值的函数:
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)
通常情况下,您会发现MaybeT单子中的代码更加简洁。
此外,从语义上讲,这两个单子非常不同...
MaybeT (Writer w) a
是一个可能失败的Writer操作,并且失败会自动处理好。而Writer w (Maybe a)
只是返回一个Maybe的Writer操作。如果该Maybe值最终变成了Nothing,则没有任何特殊情况发生。这在add1_W
函数中得到了体现,我们必须对x
进行情况分析。
选择MaybeT
方法的另一个原因是,我们可以编写对任何单子栈都通用的代码。例如,如下函数:
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)
中编写代码时,您的代码明确显示了单子的结构,使其变得不那么通用。这是add1_W
的定义:
add1_W e = do x <- e
return $ do
y <- x
return $ y + 1
只在双层单子栈中工作,而像 square
这样的函数则在更一般的设置中工作。
Writer w (IO a)
是Writer单子函子中返回IO a
的一个值,并且这与WriterT w IO a
不同——后者是在WriterT w IO
单子函子中返回一个a
的一个值。 - ErikR