Haskell 导管-分派到多个输出文件中
我试图将项目从一个管道分派到多个输出文件中,问题非常类似,但有一些不同:Haskell 导管-分派到多个输出文件中,haskell,conduit,Haskell,Conduit,我试图将项目从一个管道分派到多个输出文件中,问题非常类似,但有一些不同: 在前面的解决方案中,每个接收器都有一个过滤器,用于确定元素是否属于该接收器。在我的例子中,来自上游的每个元素都精确地到达一个文件,在有大量文件的情况下,最好只进行一次操作来决定它要到达哪个文件 这些文件是按需创建的。“选择器”函数决定下一个元素将进入哪个接收器,如果它还不存在,则使用“创建新接收器”函数创建它 例如,如果源产生:84715 而接收器选择器是模块3,则操作顺序为: Create file 2 Add 8
- 在前面的解决方案中,每个接收器都有一个过滤器,用于确定元素是否属于该接收器。在我的例子中,来自上游的每个元素都精确地到达一个文件,在有大量文件的情况下,最好只进行一次操作来决定它要到达哪个文件
- 这些文件是按需创建的。“选择器”函数决定下一个元素将进入哪个接收器,如果它还不存在,则使用“创建新接收器”函数创建它
而接收器选择器是模块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包含一个汇映射,但我无法绑定这些类型。我不确定你是否能用同一个水槽两次
我想做的事可能吗
我在Haskell还是个新手,所以任何帮助都将不胜感激
编辑
我想我可以创建一个可恢复墨水的地图,有一个用于此的库,但它取决于一个旧的非常特殊的导管版本,所以阴谋集团无法安装它。
最后,我没有找到使用前一种类型编写函数的方法,可以处理任何接收器,因此我提出了一个直接处理文件的函数:
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)
导入System.IO(hClose、openFile、IOMode(WriteMode))
导入导管
导入数据.IOData
导入符合条件的数据。可折叠为F
导入符合条件的Data.Map.Strict作为M
进口控制。单子。状态。严格
导入Data.ByteString.Char8(包)
fileDispatcherSink::
(MonadIO m,IOData c,Ord k)=>
(a->k)->
(a->c)->
(k->FilePath)->
下沉一米()
fileDispatcherSink选择器到缓存路径=
evalStateC M.empty$dispatcher
哪里
调度程序=do
下一步
设k=选择器a
h do
nh返回h
收益率(toChunked a)$$h
调度员
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 uuu函数?实现时存在概念问题
dispatcherSink_ :: (Monad m) =>
(a -> k) -> -- sink selector
(k -> Sink a m ()) -> -- new sink
Sink a m ()
。在管道中,数据从上游拉到下游,而不是推送。因此,Sink
决定是否从其上游管道请求下一个输入值。因此,您不能真正拥有Sink
s的映射,请读取输入值,然后将其馈送到Sink
s之一。您选择的Sink
可能不会决定读取输入值,它可能决定完成,然后您将如何处理输入值?您可以为该键创建一个新接收器,但它也可以决定不接受输入
因此,你很可能需要一些不同的概念,你可以将一个值和你可以最终确定的东西推到它上面,而不是一个Sink
。一个想法(未经测试):
写入文件的实现将打开一个文件,保留句柄,psPush
将只向文件中写入一个块,返回相同的对象,而psFinalize
将关闭文件
然后你可以实现这样一个变体
dispatcherSink_ :: (Monad m) =>
(a -> k) -> -- sink selector
(k -> m (PushSink a m)) -> -- new sink
Sink a m ()
它将值推送到
PushSink
s,并在没有输入的情况下完成所有值。您能介绍您迄今为止所做的尝试吗?谢谢@PetrPudlák,这是一种有趣的方法。我有一种感觉,如果有类似的情况,我正在尝试做的将更适合一种“反应管道”。在这种情况下,zipSinks函数是如何工作的?恐怕我还拿不到它的源代码。
dispatcherSink_ :: (Monad m) =>
(a -> k) -> -- sink selector
(k -> m (PushSink a m)) -> -- new sink
Sink a m ()