15

为什么 Haskell 中有两种不同的 Writer 类型的 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 产生runWriterTthunk,strict 产生<>thunk。

这给我留下了两个问题:

  1. 以上是对的,还是我误解了这里的评价?
  2. 我可以<>在不编写自己的包装器和实例的情况下完成严格吗?
4

1 回答 1

17

您的第一个观察结果是正确的,但是创建的 thunk 之间的区别很重要。

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在第一种情况下,当您将 args 传递给时,该对立即被强制执行。

在后一种情况下,这对不会立即被强制,而是我给你一个(,)充满两个非严格计算的背。

这意味着

fmap f _|_ = _|_ 

在第一种情况下,但是

fmap f _|_ = (_|_, _|_)

在第二个懒惰的情况下!

在对一对概念的不同解释下,两者都是正确的。通过假装一对是绝对意义上的一对来强迫你,它本身没有任何有趣_|_的 's 。另一方面,将域解释为非严格的。尽可能让尽可能多的程序终止,从而引导您进入该Lazy版本。

(,) e是一个完全可接受的Writer,所以这就是问题的特征。

区分的原因是它对于终止许多通过 monad 采用固定点的奇异程序很重要。您可以回答有关涉及州或作家的某些循环计划的问题,只要它们是懒惰的。

请注意,在这两种情况下,'log' 参数都不是严格的。一旦受到严格限制,您就会失去适当的关联性,并且在技术上不再是Monad. =/

因为这不是 monad,所以我们不在mtl!

有了这个,我们可以解决你的第二个问题:

不过有一些解决方法。您可以WriterState. 基本上假装你没有收到状态论据。并像您一样将其映射到状态中tell。现在您可以严格执行此操作,因为它不会作为每次绑定的一部分在您背后发生。State只是通过动作之间未修改的状态。

shout :: Monoid s => s -> Strict.StateT s m ()
shout s' = do
   s <- get
   put $! s <> s'

然而,这确实意味着你强制你的整个Statemonad 得到输出,并且不能产生部分Monoidlazily,但你得到的东西在操作上更接近于严格的程序员所期望的。有趣的是,这对 just也有效Semigroup,因为唯一的使用mempty是在你开始时有效runState

于 2013-02-01T14:37:41.120 回答