我正在尝试将管道中的项目分配到许多输出文件中,问题与Conduit - Multiple output file within the pipeline非常相似,但存在一些差异:
在上一个解决方案中,每个接收器都有一个过滤器,用于决定该元素是否属于该接收器。在我的情况下,来自上游的每个元素都精确地传递给一个文件,并且在存在大量文件的情况下,最好只进行一次操作来决定它将进入哪个文件。
< / LI>根据需要创建文件。 “选择器”函数决定下一个元素将进入哪个接收器,如果它不存在,则使用“创建新接收器”功能创建它。
例如,如果来源产量:8 4 7 1 5
接收器选择器是模块3,然后动作序列将是:
Create file 2
Add 8 to file 2
Create file 1
Add 4 to file 1
Add 7 to file 1
Add 1 to file 1
Add 5 to file 2
我正在考虑这个调度员的类型:
dispatcherSink_ :: (Monad m) =>
(a -> k) -> -- sink selector
(k -> Sink a m ()) -> -- new sink
Sink a m ()
我尝试使用evalStateC编写函数,内部StateT持有一个Sinks Map,但我无法绑定类型。我不确定你是否可以使用同一个水槽两次。
我正在尝试做甚么可能吗?
我仍然是Haskell的新手,所以任何帮助都会受到赞赏。
虽然我可以创建一个ResumableSinks的地图,但Hackage中有一个库,但它取决于一个旧的非常特殊的Conduit版本,因此cabal无法安装它。 最后我没有找到一种方法来编写前一种类型的函数,能够使用任何接收器,所以我想出了一个直接使用文件的函数:
import System.IO (hClose,openFile,IOMode(WriteMode))
import Conduit
import Data.IOData
import qualified Data.Foldable as F
import qualified Data.Map.Strict as M
import Control.Monad.State.Strict
import Data.ByteString.Char8 (pack)
fileDispatcherSink ::
(MonadIO m, IOData c,Ord k) =>
(a -> k) ->
(a -> c) ->
(k -> FilePath) ->
Sink a m ()
fileDispatcherSink selector toChunked path =
evalStateC M.empty $ dispatcher
where
dispatcher = do
next <- await
m <- get
case next of
Nothing -> liftIO $ F.traverse_ hClose m
Just a -> do
let k = selector a
h <- case M.lookup k m of
Nothing -> do
nh <- liftIO $ openFile (path k) WriteMode
put $ M.insert k nh m
return nh
Just h -> return h
yield (toChunked a) $$ sinkHandle h
dispatcher
testSource :: (Monad m) => Source m Int
testSource = yieldMany [8, 4, 7, 1, 5]
main :: IO ()
main = testSource
$$ fileDispatcherSink (`mod` 3) (pack . show) ((++ ".txt") . show)
有没有办法编写_dispatcherSink__函数?
答案 0 :(得分:2)
实施
存在概念性问题dispatcherSink_ :: (Monad m) =>
(a -> k) -> -- sink selector
(k -> Sink a m ()) -> -- new sink
Sink a m ()
。在 conduit 中,数据从上游拉到下游,而不是被推送。因此Sink
决定它是否从其上游管道请求下一个输入值。因此,您无法拥有Sink
s的地图,读取输入值,然后将其提供给其中一个Sink
。您选择的Sink
可能不会决定读取输入值,它可能决定完成,然后您将对输入值执行什么操作?您可以为该密钥创建一个新的接收器,但它也可以决定不接受该输入。
因此,您很可能需要一些不同的概念,而不是Sink
,您可以推送一个值以及您可以最终确定的内容。一个想法(未经测试):
data PushSink m i = PushSink { psPush :: i -> m (PushSink m i)
, psFinalize :: m () }
编写文件的实现会打开一个文件,保留句柄,psPush
只会将一个块写入文件,返回相同的对象,而psFinalize
将关闭文件。
然后你可以实现这样的变体
dispatcherSink_ :: (Monad m) =>
(a -> k) -> -- sink selector
(k -> m (PushSink a m)) -> -- new sink
Sink a m ()
将值推送到PushSink
s并在没有输入时将它们全部完成。