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

hpa*_*eco 10 haskell

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

main = print $ (flip evalState) "a" $ do
    forever $ put "b"
    put "c"
    get
Run Code Online (Sandbox Code Playgroud)

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

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

main = print $ S.runST $ do
        r <- newSTRef "a"
        forever $ writeSTRef r "b"
        writeSTRef r "c"
        readSTRef r
Run Code Online (Sandbox Code Playgroud)

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

Zet*_*eta 7

懒惰的Control.Monad.ST.Lazymonad 有多懒?

令人惊讶的是,它完全是懒惰的.但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
Run Code Online (Sandbox Code Playgroud)

即使ok并且oops应该这样做,我们也只能得到它的元素ok.如果我们尝试使用head oops,我们就会失败.但是,关于ok,我们可以采取任意多的要素.

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

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

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

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

怎么样readSTRef

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

main = print $ L.runST $ do
    forever $ return ()
    r <- newSTRef "a"
    readSTRef r
Run Code Online (Sandbox Code Playgroud)

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

main = print $ L.runST $ do
    forever $ return ()
    r <- newSTRef "a"
    readSTRef r
    return "a"
Run Code Online (Sandbox Code Playgroud)

… 一切都好.所以显然有一些严格的newSTRef或者readSTRef.让我们来看看它们的实现:

import qualified Data.STRef as ST

newSTRef        = strictToLazyST . ST.newSTRef
readSTRef       = strictToLazyST . ST.readSTRef
writeSTRef  r a = strictToLazyST (ST.writeSTRef r a)
Run Code Online (Sandbox Code Playgroud)

而且还有罪魁祸首.Data.STRef.Lazy实际上是通过实现Data.STRef,这是为了Control.Monad.ST.Strict.strictToLazyST只隐藏这个细节:

strictToLazyST :: ST.ST s a -> ST s a
strictToLazyST m = ST $ \s ->
Run Code Online (Sandbox Code Playgroud)

将严格的ST计算转换为惰性计算.传递给的严格状态线程strictToLazyST直到它返回的延迟状态线程的结果被要求才执行.

现在让我们放在一起:

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

所以现在ST.Lazy很懒.Data.STRef.Lazy太严格了.只要Data.STRef.Lazy是基于strictToLazyST此,这种行为就会持久.