我有一個艱難的時間來寫這個簽名pipe
:如何編寫一個管道向下遊發送從上游接收到的內容列表?
toOneBigList :: (Monad m, Proxy p) =>() -> Pipe p a [a] m r
應該簡單地採取從上游所有a
S和列表中的下游發送。
我所有的嘗試看起來都是根本打破的。
任何人都可以指向正確的方向嗎?
我有一個艱難的時間來寫這個簽名pipe
:如何編寫一個管道向下遊發送從上游接收到的內容列表?
toOneBigList :: (Monad m, Proxy p) =>() -> Pipe p a [a] m r
應該簡單地採取從上游所有a
S和列表中的下游發送。
我所有的嘗試看起來都是根本打破的。
任何人都可以指向正確的方向嗎?
有兩個pipes
爲基礎的解決方案,我就讓你選擇你更喜歡哪一個。
注意:目前還不清楚爲什麼您在下游接口上輸出列表,而不是直接將其作爲結果返回。
第一個,這是非常接近conduit
基礎的解決方案採用了即將到來的pipes-pase
,這基本上是完整的,只是需要的文檔。你可以在Github上找到latest draft。
使用pipes-parse
,溶液是相同的conduit
解決方案,彼得得到:
import Control.Proxy
import Control.Proxy.Parse
combine
:: (Monad m, Proxy p)
=>() -> Pipe (StateP [Maybe a] p) (Maybe a) [a] m()
combine() = loop []
where
loop as = do
ma <- draw
case ma of
Nothing -> respond (reverse as)
Just a -> loop (a:as)
draw
就像conduit
的await
:它從任一剩菜緩衝器(這是StateP
一部分)或請求一個值如果緩衝區爲空,則從上游啓動。 Nothing
表示文件結束。
你可以用不具有使用wrap
函數從pipes-parse
文件信號,其具有類型的端部的管:
wrap :: (Monad m, Proxy p) => p a' a b' b m r -> p a' a b' (Maybe b) m s
第二種替代方案是簡單一點。如果你想摺疊一個給定的管道,你可以這樣做,直接使用WriterP
:
import Control.Proxy
import Control.Proxy.Trans.Writer
foldIt
:: (Monad m, Proxy p) =>
(() -> Pipe p a b m()) ->() -> Pipe p a [b] m()
foldIt p() = runIdentityP $ do
r <- execWriterK (liftP . p >-> toListD >-> unitU)()
respond r
這是怎麼回事的更高級別的描述,但它需要在管道作爲傳遞一個明確的說法。這取決於你喜歡哪一個。
順便說一下,這就是爲什麼我問你爲什麼要發送一個下游價值。以上是簡單得多,如果你回到摺疊列表:
foldIt p = execWriterK (liftP . p >-> toListD)
的liftP
甚至可能不是必要的,如果p
在其代理類型完全多態性。我只是把它作爲一個預防措施。
原因pipes-parse
不提供是,它總是一個管反模式組的結果到一個列表。 pipes
有幾個很好的功能,可以不必將輸入分組到列表中,即使您試圖產生多個列表。例如,使用respond
組成,你可以有一個代理產生它會遍歷流的子集,然後注射使用該子集的處理程序:
example :: (Monad m, Proxy p) =>() -> Pipe p a (() -> Pipe p a a m()) m r
example() = runIdentityP $ forever $ do
respond $ \() -> runIdentityP $ replicateM_ 3 $ request() >>= respond
printIt :: (Proxy p, Show a) =>() -> Pipe p a a IO r
printIt() = runIdentityP $ do
lift $ putStrLn "Here we go!"
printD()
useIt :: (Proxy p, Show a) =>() -> Pipe p a a IO r
useIt = example />/ (\p -> (p >-> printIt)())
這裏有一個如何使用它的一個例子:
>>> runProxy $ enumFromToS 1 10 >-> useIt
Here we go!
1
2
3
Here we go!
4
5
6
Here we go!
7
8
9
Here we go!
10
這意味着即使需要對元素進行分組,也不需要將單個元素放入內存中。
我只給出部分答案,也許別人會有更好的答案。
據我所知,標準管道沒有檢測管道另一部分何時終止的機制。終止的第一個管道產生管道的最終結果,所有其他管道都被丟棄。因此,如果你有一個永久消耗輸入的管道(最終產生一個列表),那麼當它的上游完成時,它將沒有機會演出併產生輸出。 (這是故意的,因此上下游部分都是相互對立的。)也許這是在管道頂部的一些圖書館中解決的。
情況與conduit不同。它具有consume函數,該函數將所有輸入組合到列表中並返回(不輸出)它。就像寫一個你所需要的,是在最後輸出列表的功能,並不難:
import Data.Conduit
combine :: (Monad m) => Conduit a m [a]
combine = loop []
where
loop xs = await >>= maybe (yield $ reverse xs) (loop . (: xs))
+1謝謝你的回答。我仍然等待一個基於管道的管道......除非有人知道這種管道類型實際上是錯誤的。 –
foldIt我正在尋找。我明白,使用列表是一個反模式,但afaik,我需要建立它以將其存儲在**二進制**文件中。整個問題是:取一大(> 20000)的大文件列表(大於20Mb),從每個文件(每個文件10個Double)計算一些統計量,並將這些結果存儲在一個二進制文件中。儘管如此,即使有管道和foldIt,該程序也將失去記憶。我在其他地方做錯了什麼... –
@GiacomoTesio這是因爲你正在加載文件列表到內存中,這是什麼導致泄漏。我正在開發一個「管道目錄」,它將爲您排列目錄列表,以避免這種常見問題。 –
非常感謝。但我不認爲這些文件名是問題所在。我用'lift $ putStrLn $ nameOf $ binaryDecodedSample'添加了一行,並且所有文件實際上都列在輸出中。事實上,這個過程消耗了大量的文件名(2Gb)。 –