Haskell:懒惰的`Control.Monad.ST.Lazy` monad是多么懒惰?

时间:2014-06-06 01:49:00

标签: haskell

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

main = print $ (flip evalState) "a" $ do
    forever $ put "b"
    put "c"
    get

这很好用并输出"c"。对于严格Control.Monad.State.Strict变体,相同的代码将永远运行put "b"并挂起。

直观地说,我希望ST monad具有相同的二元性。也就是说,给出代码:

main = print $ S.runST $ do
        r <- newSTRef "a"
        forever $ writeSTRef r "b"
        writeSTRef r "c"
        readSTRef r

Control.Monad.ST.Lazy应该输出"c",而Control.Monad.ST.Strict应该会挂起。但是,它们都无限循环。我认为这是有正当理由的,例如:向前阅读 ,在调用最后一个r时尚未分配引用writeSTRef。但它感觉我们可以做得更好。

1 个答案:

答案 0 :(得分:7)

  

懒惰的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

即使okoops也应该这样做,我们只能获得ok的元素。如果我们尝试使用head oops,我们就会失败。但是,关于ok,我们可以采用任意多个元素。

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

ok, oops :: [Integer]
ok'   = map (^2) [1..]
oops' = let x = map (^2) [1..] in force x -- Control.DeepSeq.force

这是因为严格版本会评估所有状态操作,即使我们的结果不需要它们。另一方面,懒惰版本延迟了操作:

  

此模块提供与Control.Monad.ST相同的接口,但monad会延迟对状态操作的评估,直到需要依赖于它们的值。

readSTRef怎么样?

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

main = print $ L.runST $ do
    forever $ return ()
    r <- newSTRef "a"
    readSTRef r

如果我们在最后添加额外的return ...

main = print $ L.runST $ do
    forever $ return ()
    r <- newSTRef "a"
    readSTRef r
    return "a"

......一切都很好。显然newSTRefreadSTRef有一些严格的内容。让我们看看他们的implementation

import qualified Data.STRef as ST

newSTRef        = strictToLazyST . ST.newSTRef
readSTRef       = strictToLazyST . ST.readSTRef
writeSTRef  r a = strictToLazyST (ST.writeSTRef r a)

这就是罪魁祸首。 Data.STRef.Lazy实际上是通过Data.STRef实现的,适用于Control.Monad.ST.StrictstrictToLazyST只隐藏了这个细节:

strictToLazyST :: ST.ST s a -> ST s a
strictToLazyST m = ST $ \s ->
     

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

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

  • main中,我们希望print懒惰ST计算所给出的值
  • 惰性ST计算的值由懒惰readSTRef
  • 给出
  • lazy readSTRef实际上是作为严格readSTRef
  • 的惰性包装器实现的
  • 严格readSTRef评估状态,就好像它是一个严格的
  • 严格评估forever $ return ()咬我们

所以当前的ST.Lazy很懒。 Data.STRef.Lazy过于严格。只要Data.STRef.Lazy基于strictToLazyST,此行为就会持续下去。