在许多情况下,我不清楚通过将两个monad与变压器组合而不是使用两个单独的monad可以获得什么。显然,使用两个独立的monad是一件很麻烦的事情,并且可以在内部用符号表示,但是有些情况下它只是表达不够吗?
一个案例似乎是列表上的StateT:组合monad不能得到正确的类型,如果你通过像Bar这样的monad栈获得正确的类型(其中Bar a =(Reader r(List(Writer) w(身份a))),它没有做正确的事。
但我想更准确地理解monad变形金刚带来什么,当它们是否必要时,以及为什么。
让这个问题更加集中:
(关于库的不同选择,我对特定的实现细节不感兴趣,而是对monad变换器/态射正在添加的一般问题(可能是Haskell独立)的问题,作为通过堆叠一堆来组合效果的替代方案monadic型构造函数。)
(为了给出一点背景,我是一个语言学家,他正在做一个丰富蒙塔古语法的项目 - 简单地输入lambda演算用于将单词意义组成句子 - 用monad变换器堆栈。了解变换器是否真的很有帮助实际上是在为我做任何有用的事情。)
谢谢,
流便
答案 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就可以了。)
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)) }