为什么 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 : 懒惰产生 runWriterT
thunks,严格产生 <>
重击。这给我留下了两个问题:
<>
不编写我自己的包装器和实例? 最佳答案
您的第一个观察结果是正确的,但是创建的 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))
这两者之间的区别在于,当您将 args 传递给
bimap
在第一种情况下,该对立即被强制。在后一种情况下,这对不会立即被强制,而是我给你一个
(,)
后面充满了两个非严格的计算。这意味着
fmap f _|_ = _|_
在第一种情况下,但是
fmap f _|_ = (_|_, _|_)
在第二个懒惰的情况下!
在对一对概念的不同解释下,两者都是正确的。假装一对是绝对意义上的一对,这是强制你的,它没有任何有趣的
_|_
是在它自己的权利。另一方面,将域解释为非严格的。尽可能让尽可能多的程序终止,从而将您引导至 Lazy
版本。(,) e
是完全可以接受的Writer
,所以这表征了问题。区分的原因是它对于终止许多通过 monad 采用固定点的奇异程序很重要。您可以回答有关涉及 State 或作家的某些循环计划的问题,只要它们是懒惰的。
请注意,在这两种情况下,'log' 参数都不是严格的。一旦你受到严格的约束,你就会失去适当的关联性,并且在技术上不再是
Monad
. =/因为这不是一个 monad,所以我们不在
mtl
中提供它。 !有了这个,我们可以解决你的第二个问题:
不过有一些解决方法。你可以构造一个假的
Writer
在 State
之上.基本上假装你没有收到状态论据。并像您一样将其映射到状态 tell
.现在您可以严格执行此操作,因为它不会作为每次绑定(bind)的一部分在您背后发生。 State
只是通过 Action 之间未修改的状态。shout :: Monoid s => s -> Strict.StateT s m ()
shout s' = do
s <- get
put $! s <> s'
但是,这确实意味着您强制使用整个
State
monad 来获取输出,并且不能产生 Monoid
的一部分懒惰,但你得到的东西在操作上更接近严格的程序员所期望的。有趣的是,即使只使用 Semigroup
也可以使用。 , 因为 mempty
的唯一用途当你 runState
开始时有效.
关于haskell - 拥有一个惰性/严格版本的 Writer 有什么意义?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14644830/