有懒惰/严格版本的Writer有什么意义?

时间:2013-02-01 11:00:14

标签: haskell monads monad-transformers writer

为什么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')

在严格版本中,模式不是无可辩驳的,即缺少~。所以上面发生的是mk a未被评估,但存储为thunk。在严格版本中,它们被评估以检查它们是否与元组模式匹配,结果被馈送到<>。在这两种情况下,>>=在实际需要结果值之前不会被评估。 所以我理解它的方式是懒惰和严格版本都做同样的事情,除了它们在>>=定义内的不同位置有thunk:lazy产生runWriterT thunks,严格产生<> thunks。

这给我留下了两个问题:

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

1 个答案:

答案 0 :(得分:15)

你首先观察是正确的,但是创建thunks之间的这种区别很重要。

LazyStrict不是关于日志类型的严格性,而是关于对中的严格性。

这是因为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}}。