2012-08-07 51 views
21

我已经使用zipSinks :: Monad m => Sink i m r -> Sink i m r' -> Sink i m (r, r')这个,但它被认为不推荐。组合两个接收器的首选方法是什么?

+0

什么样的行为,*确切*,你想要“合并”水槽有?我试着查看'zipSinks'的旧文档和实现,但行为不容易一眼就看出来。 – 2012-08-07 15:58:29

+0

@DanBurton:'zipSinks'需要两个接收器并返回一个接收器,该接收器会产生一个具有相应接收器结果的对。例如'sizeCrc32Sink = zipSinks sizeSink crc32Sink'会计算大小和校验和。我和Oleg描述的行为一样[here](http://okmij.org/ftp/Streams.html#1enum2iter)。 – tymmym 2012-08-07 16:46:56

+0

好吧,我明白了;它基本上挂起了等待并同时将上游输出馈送到两个接收器,将输入流分成两部分。 Data.Conduit.Util的文档声明“现在有更简单的方法来处理它们的用例”,但我没有看到这种用例的更简单的方法,因为它需要深入研究管道内部实现。 – 2012-08-08 03:46:05

回答

6

((包是conduit-0.5.2.3,整个module只是为了向下兼容。))


[编辑]

所以,我简单的一元猜想(见下文)似乎是错误,即使类型是正确的。 现在,我只能猜测答案是:

替换功能仍在开发中,几乎和所有Pipe/Conduit以及类似的概念和库一样。

我等待下一个API来解决这个问题,直到那时仍然使用zipSink。 (也许这只是放错了地方。)

[/编辑]

我不是这个包familar,但不会是做一样的,因为这?

zipSinks :: Monad m => Sink i m r -> Sink i m r' -> Sink i m (r, r') 
zipSinks s1 s2 = (,) <$> s1 <*> s2 

毕竟这是一个Monad。 (FunctorApplicative

zipSinks :: Monad sink => sink r -> sink r' -> sink (r, r') 
zipSinks s1 s2 = liftM2 (,) s1 s2 
+2

类型可以,但不是语义。您的zipSinks版本将连续运行接收器,并且第一个接收器将完全消耗源代码。 – tymmym 2012-08-15 05:56:26

7

编辑

考虑在此之后,我不认为这是可能的Data.Conduit的当前版本。管道不是类别,所以&&&是不可能的。我不可能想到从上游获得结果,逐步将它们提供给两个接收器,并在第一个接收器结束时进行短路。 (虽然我不认为Data.Conduit.Util.zipSinks这样短路,但它似乎是非常可取的。)除了当然,模式匹配在两个水槽(如包中的zipSinks),但这就是我们试图避免在这里。

这就是说,我会在这里被证明是错误的。


这不是很漂亮,但你可以用一种明显的方式做到这一点。

首先进口:

module Main where 

import Control.Monad.Trans 
import Data.Conduit 
import qualified Data.Conduit.Binary as CB 
import qualified Data.Conduit.List as CL 
import qualified Data.Conduit.Text as CT 
import qualified Data.Conduit.Util as CU 
import Data.Maybe 
import Data.Text (unpack) 

现在对于zipSinks。基本上,您要创建一个接收器,从上游获取输入,并将其分别发送给每个子接收器。在这种情况下,我用CL.sourceList来做到这一点。如果await返回Nothing,maybeToList返回一个空列表,所以子接收器也在没有输入的情况下运行。最后,每个子宿的输出被输入到元组中。

zipSinks :: Monad m => Sink i m r -> Sink i m r' -> Sink i m (r, r') 
zipSinks s1 s2 = do 
    l <- fmap maybeToList await 
    o1 <- lift $ CL.sourceList l $$ s1 
    o2 <- lift $ CL.sourceList l $$ s2 
    return (o1, o2) 

以下是使用zipSinks的一些示例。它似乎在IO内部以及其外部均正常工作,并且在我进行的少数测试中,输出与使用旧的zipSinks创建的zipped'的输出相匹配。

doubleHead :: Monad m => Sink Int m (Maybe Int) 
doubleHead = await >>= return . fmap (2*) 

-- old version 
zipped' :: Monad m => Sink Int m (Maybe Int, Maybe Int) 
zipped' = CU.zipSinks CL.head doubleHead 

-- new version 
zipped :: Monad m => Sink Int m (Maybe Int, Maybe Int) 
zipped = zipSinks CL.head doubleHead 

fromList = CL.sourceList [7, 8, 9] $$ zipped 
-- (Just 7, Just 14) 

fromFile :: String -> IO (Maybe Int, Maybe Int) 
fromFile filename = runResourceT $ 
     CB.sourceFile filename 
    $= CB.lines 
    $= CT.decode CT.utf8 
    $= CL.map (read . unpack) 
    $$ zipped 

-- for a file with the lines: 
-- 
-- 1 
-- 2 
-- 3 
-- 
-- returns (Just 1, Just 2) 
+0

不错! (注意,你可以为'doubleHead'写'await >> = return。fmap(2 *)',同样''l -fmap mayToList await'而不是在'zipSinks'中使用'input', Data.Conduit.Internals'是一个额外的导入?) – huon 2012-08-15 19:31:29

+0

是的,我意识到我可能可以在某些地方使用函子。对于Haskell来说,我仍然足够了,这对我来说通常是第二遍编辑,不幸的是。 是的,'Data.Conduits.Internals'是无关的。最初,我正在使用'sinkToPipe'。 感谢您指出这些。我会更新答案。 – Eric 2012-08-15 20:14:41

+2

您的zipSinks版本只包含第一个元素。例如'runResourceT $ CL.sourceList [1,2,3] $$ zipSinks(CL.take 2)(CL.take 2)''会返回'([1],[1])',但应该是'([1 ,2],[1,2])'。 – tymmym 2012-08-16 05:49:07

相关问题