为什么 monad 转换器与堆叠 monad 不同?
Why are monad transformers different to stacking monads?
在许多情况下,我不清楚将两个 monad 与转换器组合而不是使用两个单独的 monad 可以获得什么。显然,使用两个单独的 monad 很麻烦,并且可以在 do 表示法中包含 do 表示法,但是否存在表达力不够的情况?
一个案例似乎是 List 上的 StateT:组合 monad 不会让你得到正确的类型,如果你确实通过一堆 monad 获得正确的类型,比如 Bar (其中 Bar a = (Reader r (List (Writer w (Identity a))),它没有做正确的事情。
但我希望更全面地了解 monad 转换器究竟为 table 带来了什么,何时需要,何时不需要,以及为什么。
为了让这个问题更有针对性:
- 没有相应转换器的 monad 的实际示例是什么(这将有助于说明转换器可以做什么而只是堆叠 monad 不能)。
- StateT 和 ContT 是否是唯一提供不等同于它们与 m 组合的类型的转换器,对于底层 monad m(无论它们的组合顺序如何)。
(我对关于不同库选择的特定实现细节不感兴趣,而是一般的(可能 Haskell 独立的)问题,即添加什么 monad transformers/morphisms 作为替代通过堆叠一堆单子类型构造函数来组合效果。)
(为了提供一点背景信息,我是一名语言学家,正在做一个项目来丰富 Montague 语法 - 简单地键入 lambda 演算,将词义组合成句子 - 使用 monad 转换器堆栈。理解它真的很有帮助变形金刚是否真的对我有用。)
谢谢,
鲁本
What is an actual example of a monad with no corresponding transformer (this would help illustrate what transformers can do that just stacking monads can't).
IO
和 ST
是这里的典型示例。
Are StateT and ContT the only transformers that give a type not equivalent to the composition of them with m, for an underlying monad m (regardless of which order they're composed.)
不,ListT m a
不是(同构于)[m a]
:
newtype ListT m a =
ListT { unListT :: m (Maybe (a, ListT m a)) }
为了回答您关于 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) }
使用 ~~
表示 "structurally similar to" 我们有:
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
和 >>=
class 函数做的事情非常不同,具体取决于
它们属于哪个 monad。
让我们考虑一下 (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) -- 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 中的代码更简洁。
此外,从语义上讲,这两个 monad 非常不同...
MaybeT (Writer w) a
是一个可以失败的 Writer-action,失败是
自动为您处理。 Writer w (Maybe a)
只是一个作家
return 可能是一个动作。如果那个 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
的 return 值不会针对 Writer String (Maybe Int)
进行类型检查,因为 square
不会 return 一个 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 可以获得什么。显然,使用两个单独的 monad 很麻烦,并且可以在 do 表示法中包含 do 表示法,但是否存在表达力不够的情况?
一个案例似乎是 List 上的 StateT:组合 monad 不会让你得到正确的类型,如果你确实通过一堆 monad 获得正确的类型,比如 Bar (其中 Bar a = (Reader r (List (Writer w (Identity a))),它没有做正确的事情。
但我希望更全面地了解 monad 转换器究竟为 table 带来了什么,何时需要,何时不需要,以及为什么。
为了让这个问题更有针对性:
- 没有相应转换器的 monad 的实际示例是什么(这将有助于说明转换器可以做什么而只是堆叠 monad 不能)。
- StateT 和 ContT 是否是唯一提供不等同于它们与 m 组合的类型的转换器,对于底层 monad m(无论它们的组合顺序如何)。
(我对关于不同库选择的特定实现细节不感兴趣,而是一般的(可能 Haskell 独立的)问题,即添加什么 monad transformers/morphisms 作为替代通过堆叠一堆单子类型构造函数来组合效果。)
(为了提供一点背景信息,我是一名语言学家,正在做一个项目来丰富 Montague 语法 - 简单地键入 lambda 演算,将词义组合成句子 - 使用 monad 转换器堆栈。理解它真的很有帮助变形金刚是否真的对我有用。)
谢谢,
鲁本
What is an actual example of a monad with no corresponding transformer (this would help illustrate what transformers can do that just stacking monads can't).
IO
和 ST
是这里的典型示例。
Are StateT and ContT the only transformers that give a type not equivalent to the composition of them with m, for an underlying monad m (regardless of which order they're composed.)
不,ListT m a
不是(同构于)[m a]
:
newtype ListT m a =
ListT { unListT :: m (Maybe (a, ListT m a)) }
为了回答您关于 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) }
使用 ~~
表示 "structurally similar to" 我们有:
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
和 >>=
class 函数做的事情非常不同,具体取决于
它们属于哪个 monad。
让我们考虑一下 (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) -- 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 中的代码更简洁。
此外,从语义上讲,这两个 monad 非常不同...
MaybeT (Writer w) a
是一个可以失败的 Writer-action,失败是
自动为您处理。 Writer w (Maybe a)
只是一个作家
return 可能是一个动作。如果那个 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
的 return 值不会针对 Writer String (Maybe Int)
进行类型检查,因为 square
不会 return 一个 Maybe
。
当您在 Writer String (Maybe Int)
中编写代码时,您的代码明确地揭示了
monad 的结构使其不那么通用。 add1_W
的这个定义:
add1_W e = do x <- e
return $ do
y <- x
return $ y + 1
仅适用于两层 monad 堆栈,而像 square
这样的函数
在更通用的环境中工作。