Haskell:替换monad变换器堆栈中的mapM以实现惰性求值(没有空间泄漏)

mcm*_*yer 1 monads haskell lazy-evaluation monad-transformers

已经讨论过,mapM这里本来就不是懒惰的,例如这里这里.现在我正在努力解决这个问题的变化,其中mapM问题是在monad变换器堆栈内部.

这是一个函数,它取自我在gist.github.com上使用的LevelDB的具体,工作(但是空间泄漏)的例子:

-- read keys [1..n] from db at DirName and check that the values are correct
doRead :: FilePath -> Int -> IO ()
doRead dirName n = do
    success <- runResourceT $ do
        db <- open dirName defaultOptions{ cacheSize= 2048 }
        let check' = check db def in        -- is an Int -> ResourceT IO Bool
            and <$> mapM check' [1..n]      -- space leak !!!
    putStrLn $ if success then "OK" else "Fail"
Run Code Online (Sandbox Code Playgroud)

此函数读取与键对应的值[1..n]并检查它们是否正确.ResourceT IO amonad 里面的麻烦线是

and <$> mapM check' [1..n]
Run Code Online (Sandbox Code Playgroud)

一个解决办法是使用流媒体库,比如pipes,conduit等但这些似乎比较重,我根本不知道如何在这种情况下使用它们.

我调查的另一条路径ListT就像这里建议的那样.但是ListT.fromFoldable :: [Bool]->ListT BoolListT.fold :: (r -> a -> m r) -> r -> t m a -> mr(where m= IOand a,r= Bool)的类型签名与手头的问题不匹配.

什么是摆脱空间泄漏的"好方法"?

更新:请注意,此问题与monad变换器堆栈无关!以下是建议解决方案的摘要:

1)使用Streaming:

import Streaming
import qualified Streaming.Prelude as S
S.all_ id (S.mapM check' (S.each [1..n]))
Run Code Online (Sandbox Code Playgroud)

2)使用Control.Monad.foldM:

foldM (\a i-> do {b<-check' i; return $! a && b}) True [1..n]
Run Code Online (Sandbox Code Playgroud)

3)使用 Control.Monad.Loops.allM

allM check' [1..n]
Run Code Online (Sandbox Code Playgroud)

dan*_*iaz 5

我知道你提到你不想使用流媒体库,但你的问题似乎很容易通过流媒体解决而不会过多地改变代码.

import Streaming
import qualified Streaming.Prelude as S
Run Code Online (Sandbox Code Playgroud)

我们使用each [1..n]而不是[1..n]获取元素流:

each :: (Monad m, Foldable f) => f a -> Stream (Of a) m ()
Run Code Online (Sandbox Code Playgroud)

流式传输纯净的可折叠容器的元素.

(我们也可以写一些类似的东西S.take n $ S.enumFrom 1).

我们用S.mapM check'而不是mapM check':

mapM :: Monad m => (a -> m b) -> Stream (Of a) m r -> Stream (Of b) m r
Run Code Online (Sandbox Code Playgroud)

使用monadic操作的结果替换流的每个元素

然后我们用以下方法折叠布尔流S.all_ id:

all_ :: Monad m => (a -> Bool) -> Stream (Of a) m r -> m Bool
Run Code Online (Sandbox Code Playgroud)

把它们放在一起:

S.all_ id (S.mapM check' (S.each [1..n]))
Run Code Online (Sandbox Code Playgroud)

与您开始使用的代码没有太大区别,也不需要任何新的运算符.