2016-10-29 11 views
1

mapMは本質的に怠惰ではないことがすでに議論されています。 hereおよびhere。今私は問題のmapMがモナド変圧器スタックの深いところにあるこの問題のバリエーションに苦しんでいます。Haskell:怠惰な評価を達成するために、モナドトランススタックでmapMを置き換えてください(スペースリークはありません)。

ここで私は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" 

この関数は、彼らがすべてであることをキー[1..n]とチェックに対応する値を読み込みます正しい。 ResourceT IO aモナド内部の面倒ラインは

and <$> mapM check' [1..n] 

一つの解決策は、これらはかなり重いように見えるなどpipesconduit、としてではなく、ストリーミングライブラリを使用することであろうと私は、このすべてでそれらを使用するかどうかはわかりません状況。

私が調べたもう1つのパスはListTです(推奨here)。しかし、タイプシグネチャListT.fromFoldable :: [Bool]->ListT BoolListT.fold :: (r -> a -> m r) -> r -> t m a -> mrm = IOa,r = Bool)は、手元の問題と一致しません。

スペースリークを解消するにはどうすればいいですか?

更新:この問題は、モナドトランススタックとは関係ありません。

1)Streamingを使用する:ここで提案されたソリューションの概要です

import Streaming 
import qualified Streaming.Prelude as S 
S.all_ id (S.mapM check' (S.each [1..n])) 

2)Control.Monad.foldM使用:Control.Monad.Loops.allM

allM check' [1..n] 
+0

私が与えた答えのほかに、あなたはプレーン定期的に使用した場合に何が起こりますか ' 「Control.Monad」から「foldM」ですか?あなたはそのようにリストをマテリアライズすることを避けることができるようです。 – danidiaz

+0

私はこれをここで試しました: 'foldM(\ a i-> do {b <-check 'i; return a $ && b})True [1..n]'。 'foldM' =' foldlM'も '> ='のシーケンスです。 – mcmayer

+0

@mcmayer、 'return $! (現在は 'foldM'は評価の前に'((... && True)&& True)&& True'のようなサンクを返しています)。それで解決できない場合は、おそらく漏れを誤診していると思います。 – luqui

答えて

5

を使用して

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

3)私はあなたを知っていますストリーミングライブラリを使用したくないと言いますが、あなたのprobレームはコードをあまり変更しないでstreamingで解決するのがかなり簡単です。純粋な、折りたたみ式コンテナの要素をストリーミング

each :: (Monad m, Foldable f) => f a -> Stream (Of a) m() 

:私たちは、要素のストリームを取得するためにeach [1..n]の代わり[1..n]を使用

import Streaming 
import qualified Streaming.Prelude as S 

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 

はモナド行動

そして、我々はS.all_ idとブール値のストリームを倍の結果とストリームの各要素を置き換えます。

all_ :: Monad m => (a -> Bool) -> Stream (Of a) m r -> m Bool 

すべて一緒にそれを置く:あなたが開始したコードから、あまりにも異なっていない

S.all_ id (S.mapM check' (S.each [1..n])) 

、との新たなオペレータを必要とせずに。

+0

はい、それは仕事です! – mcmayer

3

モナドループパッケージから、allMが必要だと思います。

そして、それだけでallM check' [1..n]

だろう(またはあなたはそれをコピーするために、かなり小さな関数のインポートをしたくない場合。)

+0

これはこれまでの最も簡単な解決策です! – mcmayer

関連する問題