在许多情况下,我不清楚将两个monad与变压器组合而不是使用两个单独的monad可以获得什么。显然,使用两个独立的monad是一件麻烦事,并且可能涉及到符号内部的符号,但是有些情况下它只是表达不够吗?
一个案例似乎是列表上的StateT:组合monads不能得到正确的类型,如果你通过像Bar这样的monad堆栈获得正确的类型(其中Bar a =(Reader r(List(Writer w(Identity) a))),它没有做正确的事情。
但是我想更准确地理解monad变压器带来什么,当它们是否必要时,以及为什么。
为了使这个问题更加集中:
(关于库的不同选择,我对特定的实现细节不感兴趣,而是对monad变换器/态射正在添加的一般问题(可能是Haskell独立)的问题,作为通过堆叠一堆monadic类型构造函数来组合效果的替代方法。)
(为了给出一点背景,我是一个语言学家,正在做一个丰富蒙塔古语法的项目 - 简单地输入lambda演算,用于将单词意义组成句子 - 用monad变换器堆栈。理解变换器是否真的在做真的很有帮助对我有用的任何东西。)
谢谢,
鲁本
要回答关于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
这样的函数在更常规的设置中工作。
什么是没有相应变压器的monad的实际示例(这将有助于说明变压器可以做什么只是堆叠monad不能)。
IO
和ST
是这里的典型例子。
StateT和ContT是唯一的变换器,它们给出的类型与m的组成不等同于m,对于底层monad m(无论它们是由哪个顺序组成的。)
不,ListT m a
不是(同构的)[m a]
:
newtype ListT m a =
ListT { unListT :: m (Maybe (a, ListT m a)) }