为什么Haskell中有两个不同的Writer类型monad?直觉上,阅读“严格的作家monad”意味着<>
是严格的,因此日志中没有thunk积累。但是,查看源代码,事实证明并非如此:
-- Lazy Writer
instance (Monoid w, Monad m) => Monad (WriterT w m) where
-- ...
m >>= k = WriterT $ do
~(a, w) <- runWriterT m
~(b, w') <- runWriterT (k a)
return (b, w <> w')
在严格版本中,模式不是无可辩驳的,即缺少~
。所以上面发生的是m
和k a
未被评估,但存储为thunk。在严格版本中,它们被评估以检查它们是否与元组模式匹配,结果被馈送到<>
。在这两种情况下,>>=
在实际需要结果值之前不会被评估。
所以我理解它的方式是懒惰和严格版本都做同样的事情,除了它们在>>=
定义内的不同位置有thunk:lazy产生runWriterT
thunks,严格产生<>
thunks。
这给我留下了两个问题:
<>
吗?答案 0 :(得分:15)
你首先观察是正确的,但是创建thunks之间的这种区别很重要。
Lazy
和Strict
不是关于日志类型的严格性,而是关于对中的严格性。
这是因为Haskell中的一对有两种可能的方法来更新它。
bimap f g (a,b) = (f a, g b)
或
bimap f g ~(a,b) = (f a, g b)
后者与
相同bimap f g p = (f (fst p), g (snd p))
这两者之间的区别在于,当您在第一种情况下将参数传递给bimap
时,该对将立即被强制使用。
在后一种情况下,该对不会立即被强制,但我会向你发送一个(,)
后面的两个非严格计算。
这意味着
fmap f _|_ = _|_
在第一种情况下
fmap f _|_ = (_|_, _|_)
在第二个lazier对案件中!
对于一对概念的不同解释,两者都是正确的。一个人被强加在你身上,假装一对是绝对意义上的一对,它本身没有任何有趣的_|_
。另一方面,域名的解释是非严格的。尽可能让你尽可能多地终止程序,将你带到Lazy
版本。
(,) e
是完全可以接受的Writer
,因此这是问题的特征。
区别的原因在于终止许多通过monad固定点的外来程序。你可以回答有关某些涉及国家或作家的循环程序的问题,只要它们是懒惰的。
注意,在'log'参数中,这两种情况都不严格。一旦你严格要求你失去适当的关联性并在技术上停止成为Monad
。 = /
因为这不是monad,我们不会在mtl
中提供它!
有了这个,我们可以解决你的第二个问题:
但是有一些解决方法。您可以在Writer
之上构建虚假State
。基本上假装你没有交出国家的论点。然后就像tell
那样进入状态。现在你可以严格执行此操作,因为它不会作为每个绑定的一部分发生在你背后。 State
只是通过动作之间未经修改的状态传递。
shout :: Monoid s => s -> Strict.StateT s m ()
shout s' = do
s <- get
put $! s <> s'
这确实意味着你强迫你的整个State
monad得到输出,并且不能懒惰地产生Monoid
的部分但是你得到的东西在操作上更接近严格的程序员会期望。有趣的是,即使只使用Semigroup
,这也是有效的,因为mempty
只能在runState
开始使用{{1}}。