管道 - 分发到多个输出文件

时间:2014-07-30 20:09:46

标签: haskell conduit

我正在尝试将管道中的项目分配到许多输出文件中,问题与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__函数?

1 个答案:

答案 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并在没有输入时将它们全部完成。