Haskell 哈斯凯尔:懒惰的控制有多懒。蒙纳德。圣。懒惰的蒙纳德?

Haskell 哈斯凯尔:懒惰的控制有多懒。蒙纳德。圣。懒惰的蒙纳德?,haskell,Haskell,我一直在试验严格和懒惰的STmonad,但我不清楚每个monad的懒惰程度。 例如,使用lazyControl.Monad.State.lazyMonad,我们可以编写: main = print $ (flip evalState) "a" $ do forever $ put "b" put "c" get 这可以正常工作并输出“c”。同样,strictControl.Monad.State.strict变量的相同代码将永远运行put“b”,并挂起 直观地说,我希望

我一直在试验严格和懒惰的
ST
monad,但我不清楚每个monad的懒惰程度。 例如,使用lazy
Control.Monad.State.lazy
Monad,我们可以编写:

main = print $ (flip evalState) "a" $ do
    forever $ put "b"
    put "c"
    get
这可以正常工作并输出
“c”
。同样,strict
Control.Monad.State.strict
变量的相同代码将永远运行
put“b”
,并挂起

直观地说,我希望
ST
单子也具有同样的二元性。也就是说,给定代码:

main = print $ S.runST $ do
        r <- newSTRef "a"
        forever $ writeSTRef r "b"
        writeSTRef r "c"
        readSTRef r
main=print$S.runST$do
R
lazy
Control.Monad.ST.lazy
Monad有多懒

令人惊讶的是,它完全是懒惰的。但是
Data.STRef.Lazy
不是

ST.Lazy
是懒惰的 让我们先关注另一个示例:

import qualified Control.Monad.ST as S
import qualified Control.Monad.ST.Lazy as L

squared :: Monad m => m [Integer]
squared = mapM (return . (^2)) [1..]

ok, oops :: [Integer]
ok   = L.runST squared
oops = S.runST squared
即使
ok
oops
应该做同样的事情,我们也只能得到
ok
的元素。如果我们尝试使用
头部oops
,我们将失败。然而,关于
ok
,我们可以取任意多个元素

或者,将它们与非一元平方列表进行比较,它们的行为如下:

ok, oops :: [Integer]
ok'   = map (^2) [1..]
oops' = let x = map (^2) [1..] in force x -- Control.DeepSeq.force
这是因为严格版本评估所有状态操作,即使我们的结果不需要这些操作。另一方面,延迟版本会延迟操作:

该模块提供了与Control.Monad.ST相同的接口,只是Monad延迟状态操作的评估,直到需要一个取决于它们的值

读码器呢? 现在让我们再次关注您的示例。请注意,我们可以使用更简单的代码获得无限循环:

main = print $ L.runST $ do
    forever $ return ()
    r <- newSTRef "a"
    readSTRef r
…一切都很好。显然,在
newSTRef
readSTRef
中有严格的规定。让我们看看他们的:

罪魁祸首来了
Data.STRef.Lazy
实际上是通过
Data.STRef
实现的,这意味着
控制.Monad.ST.Strict
。仅隐藏此详细信息:

将严格的ST计算转换为惰性计算。传递给
strictToLazyST
的严格状态线程在需要它返回的惰性状态线程的结果之前不会执行

现在让我们把事情放在一起:

  • main
    中,我们希望
    打印
    惰性
    ST
    计算给出的值
  • 惰性
    ST
    计算的值由惰性
    readSTRef
  • lazy
    readSTRef
    实际上是作为严格的
    readSTRef
  • strict
    readSTRef
    对状态进行评估,就像它是一个严格的状态一样
  • forever$return()
    的严格评估让我们不寒而栗

因此当前的
ST.Lazy
已经足够懒了
数据.STRef.Lazy
太严格了。
只要
数据.STRef.Lazy
基于
strictToLazyST
,这种行为就会持续。

你可以使用
Control.Monad.Lazy.Unsafe.unsafeInterleaveST$forever$writeSTRef r“b”
来获得你想要的惰性。虽然我不知道你的问题,但我不能说这样做是否安全。是的,这就是我找到的解决办法@luqui也在[thread]上对此进行了评论。
forever
有点夸张,因为看起来你至少需要追溯修改的历史(我希望不是在同一个参考上评估修改)到创建点。哦,我应该指出,
unsafeInterleaveST
意味着推迟对其参数的求值,直到需要结果值。这意味着如果忽略
unsafeInterleaveST
的结果(如我的评论中所述),您也可以删除该行。
unsafeInterleave*
函数实际上是用于创建诸如列表之类的codata。感谢您花时间,它检查出来了。从前面的讨论来看,它似乎是数据。STRef
出于某种原因更为严格。我甚至冒着风险说,它不可能在ge完全懒惰一般来说,由于与普通的
状态
单子不同,它的分配是由
运行状态
执行的,因此
状态之前的所有内容都可以忽略。put
需要为每个
newSTRef
动态分配新空间。然而,我们想到了一个可能的优化:for
STRef
在不同的类型上,
newSTRef
readSTRef
writeSTRef
操作可以惰性地交错。@hpacheco:About
State
:它本身不会被忽略,而是使用了一种无可辩驳的模式,这使得它足够懒惰,以便我们能够产生这种效果。
STRef上的操作改变状态,无论是
现实世界
,还是执行线程
。这在
read | new | write | modifyMutVar
中得到了深入的实现。真正懒惰的
STRef
需要收集动作,然后在解析全部动作后将其折叠。这是否可能没有
unsafeInterleaveST
,我不知道。我对Haskell还是相当陌生:)。
main = print $ L.runST $ do
    forever $ return ()
    r <- newSTRef "a"
    readSTRef r
    return "a"
import qualified Data.STRef as ST

newSTRef        = strictToLazyST . ST.newSTRef
readSTRef       = strictToLazyST . ST.readSTRef
writeSTRef  r a = strictToLazyST (ST.writeSTRef r a)
strictToLazyST :: ST.ST s a -> ST s a
strictToLazyST m = ST $ \s ->