4

パイプライブラリを使用して、あるソースからデータを読み取り、それをモノイド的に蓄積するプログラムを作成したいと思います(たとえば、を使用してSum)。これを行う最も簡単な方法は、

 import Control.Proxy as 
 import Data.Monoid (Sum)

 main = do
     let source = enumFromToS (0::Int) 5
     a <- runWriterT $ runProxy $ source >-> foldD Sum
     print a

もちろん、これは小さなソースでは機能しますが、大きな入力は、WriterTアキュムレータの怠惰な性質のために恐ろしいスタックオーバーフローを引き起こします。

ありがたいことに、これはpipesこれを予期しているようで、WriterPプロキシに厳密なアキュムレータを提供します。残念ながら、このプロキシを取り巻くドキュメントはかなりまばらです。少し突っ込んだ後(そして問題を単純化して、代わりにダウンストリーム要素ごとに1を累積する)、私はこのプログラムに行きました、

import Control.Proxy
import Control.Proxy.Trans.Writer
import Data.Monoid (Sum)

main = do
    let source = enumFromToS (0::Int) 5
    a <- runProxy $ runWriterK $ source >-> \x->tell (Sum 1::Sum Int)
    print a

もちろん、このプログラムは、6ではなく1に累積されるため、簡略化されたタスクを正しく実行しません。私が間違っていない場合、この動作は、パイプが終了する前に1つの要素のみを読み取るという事実によって説明されます。入力が終わるまで繰り返すために、私は次のことを思いついた、

import Control.Proxy
import Control.Proxy.Trans.Writer
import Data.Monoid (Sum)

main = do
    let source = enumFromToS (0::Int) 5
    a <- runProxy $ runWriterK $ source >-> fold Sum
    print a

fold :: (Monad m, Proxy p, Monoid w) => (a -> w) -> a' -> WriterP w p a' a a' a m r
fold f = go
  where go x = do a <- request x
                  tell $ f a
                  x' <- respond a
                  go x'

ただし、このコードはアキュムレータ0を返します。これはなぜですか?私のような機能はありfoldますpipesか?

の多くのユースケースpipesが大規模なデータセットを処理する長時間実行プロセスであることを考えると、フォールドインを厳密ではなくControl.Proxy.Prelude厳密に構築することは意味がありませんか?現在、のプロキシトランスフォーマーは二流市民であり、存在しているように感じますが、非常に便利なコンビネータの多くが不足しています。WriterPWriterTpipesWriterT

4

2 に答える 2

7

pipes-3.3Hackageにアップロードしたばかりのでこの問題を修正したので、新しい回答を追加します。パイプの背後にある理論は、期待していたグローバルな動作がずっと正しい動作WriterPであり、パイプ内で折りたたむことができるようにグローバルに動作することを示しています。

例を変更して、次を使用して実装することを示しましたpipes-3.3

import Control.Proxy
import Control.Proxy.Trans.Writer

main = do
    let source = enumFromToS (0::Int) 5
    a <- runProxy $ execWriterK $ source >-> sumD
    print a

パイプライン内のフォールドの結果を取得できるようになりました。たとえば、これは完全に有効です。

chunksOf :: (Monad m, Proxy p) => Int -> () -> Pipe p a [a] m r
chunksOf n () = runIdentityP $ forever $ do
    -- The unitU discards the values that 'toListD' reforwards
    as <- execWriterK (takeB_ n >-> toListD >-> unitU) ()
    respond as

使用例は次のとおりです。

>>> runProxy $ getLineS >-> takeWhileD (/= "quit") >-> chunksOf 3 >-> printD
1<Enter>
2<Enter>
3<Enter>
["1","2","3"]
4<Enter>
5<Enter>
6<Enter>
["4","5","6"]
quit

初めて答えを間違えてすみません!

于 2013-05-06T07:07:43.953 に答える
4

プロキシトランスフォーマーはローカルで動作しますが、ベースモナドはグローバルで動作することに注意してください。つまり、WriterP各プロキシが独自のアキュムレータを維持し、最初に終了するプロキシがどのアキュムレータを返すかを決定します。アキュムレータが返さ0れる理由は、列挙パイプが最初に返され、何も蓄積されていなかったためです。

WriterPフォールドは、私がそのタイプを制御できるという理由だけで厳密です(のタイプは制御できません)transformers。プロキシプレリュードの怠惰なフォールドの厳密な代替となることを意図したことはありません。使用するための正しい厳密な代替手段はですfoldlD'

foldlD' mappendこれは基本的に厳密な折り畳みであることに注意してください。特に、初期状態としてを使用してベースモナドをWriter実行する場合はそうです。Statemempty

あなたの場合、あなたはそれをさらに簡単に使うことができます:

main = do
    let source = enumFromToS (0::Int) 5
    a <- (`runStateT` 0) $ runProxy $ source >-> foldlD' (+)
    print a

これにより、入力が厳密に折りたたまれます。

于 2013-01-24T19:39:08.103 に答える