提问人:Clinton 提问时间:11/13/2023 更新时间:11/14/2023 访问量:48
使用另一根导管对导管进行部分处理
Partially processing a conduit using another conduit
问:
我希望制作一个具有以下签名的函数(我认为):
partialProcessConduit :: forall m a b r. Monad m
=> (a -> (b, Maybe (ConduitT () a m ()) ))
-> ConduitT b Void m r
-> ConduitT () a m ()
-> m (r, ConduitT () a m ())
partialProcessConduit splitFunc consumingConduit sourceConduit
它基本上做到了以下几点:
- 重复从管道中获取 type 的值。
a
sourceConduit
- 将函数应用于该值。
splitFunc
a
- 将值从
b
splitFunc
consumingConduit
- IF 返回(即不)返回其返回对的第二部分 THEN
splitFunc
Just (some conduit)
Nothing
- “close up”,并得到结果值
consumingConduit
r
- 返回一个带有 的“其余”的导管,但在它前面附加了 Just 中的导管。
sourceConduit
- “close up”,并得到结果值
我实际上已经取得了接近这个成就(提前为蹩脚的命名道歉)。看这里:
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE ScopedTypeVariables #-}
import Conduit (ConduitT, SealedConduitT, unsealConduitT, ($$+), await)
import Data.Void (Void)
import qualified Control.Arrow as Arrow
import Data.DList (DList)
partialProcessConduitInMemory :: forall m a b r. Monad m
=> (a -> (b, Maybe (ConduitT () a m ())))
-> (DList b -> r)
-> ConduitT () a m ()
-> m (r, ConduitT () a m ())
partialProcessConduitInMemory splitFunc collapseDList sourceConduit = do
(sc :: SealedConduitT () a m (), (result :: r, leftOver :: ConduitT () a m ())) <- x
pure (result, leftOver >> unsealConduitT sc)
where
x :: m (SealedConduitT () a m (), (r, ConduitT () a m ()))
x = sourceConduit $$+ g
g :: ConduitT a Void m (r, ConduitT () a m ())
g = Arrow.first collapseDList <$> go mempty
go :: DList b -> ConduitT a Void m (DList b, ConduitT () a m ())
go blockList = await >>= \case
Nothing -> pure (blockList, pure ())
Just block -> case splitFunc block of
(transformedBlock, Nothing) -> go $ blockList <> pure transformedBlock
(transformedBlock, Just leftOver) -> pure (blockList <> pure transformedBlock, leftOver)
这几乎是我想要的。请注意,此处的类型签名与上面相同,除了第二个参数。在这里,我没有传递一个使用元素作为第二个参数的管道接收器,而是将它们收集在“DList”中。我宁愿能够使用管道接收器来使用管道源的第一部分,而不是收集列表中的所有元素并对其进行处理。
我是否可以在这里使用导管水槽而不是 ,如果是这样,我需要进行什么样的调整?我考虑过在循环中将元素推送到接收器中,而不仅仅是附加它们,然后以某种方式获得结果,但我无法很好地处理这些类型。任何帮助都表示赞赏。DList
go
runConduit
r
答:
我想你想要这样的东西:
{-# LANGUAGE ScopedTypeVariables #-}
partialProcessConduit :: forall m a b r. Monad m
=> (a -> (b, Maybe (ConduitT () a m ()) ))
-> ConduitT b Void m r
-> ConduitT () a m ()
-> m (r, ConduitT () a m ())
partialProcessConduit f snk src = do
(rest2, (mrest1,r)) <- src $$+ fuseBoth loop snk
pure (r, maybe id (>>) mrest1 (unsealConduitT rest2))
where loop :: ConduitT a b m (Maybe (ConduitT () a m ()))
loop = do ma <- await
case ma of
Just a -> do
let (b, mrest) = f a
yield b
case mrest of
Nothing -> loop
Just rest -> pure (Just rest)
Nothing -> pure Nothing
这里的 conduit 具有 类型 ,因此它输入 s 并输出 s,直到 (AKA ) 返回前缀 conduit,在这种情况下,它返回该 conduit。如果从不返回管道,则返回 .loop
ConduitT a b m (Maybe (ConduitT () a m ())
a
b
f
splitFunc
Just
splitFunc
Nothing
现在,我们可以 ,其类型为 。这会将 s 从 中下沉到 中,从 返回前缀 conduit(如果有的话)和从 返回。fuseBoth loop snk
ConduitT a Void m (Maybe (ConduitT () a m (), r)
b
loop
snk
splitFunc
r
snk
最后,我们可以.这将运行整个管道 sourcing s from 并将 s sinking 到 ,直到返回前缀 conduit,此时它将返回:src $$+ fuseBoth loop snk
a
src
b
snk
splitFunc
(SealedConduitT () a m (), (Maybe (ConduitT () a m ()), r))
令人难以置信的是,密封的导管是 剩下的,导管是 返回的“前缀”导管,最后一个是 的返回值。剩下的就是将其粘合到适当的返回值中。src
Maybe
splitFunc
r
snk
这似乎可以按照以下测试工作:
main :: IO ()
main = do
(r, c) <- partialProcessConduit foo (printC >> pure 999) (yieldMany [1,2,3,4,7,8,9])
runConduit (c .| printC)
print r
where foo 4 = (42, Just (yieldMany [5,6]))
foo n = (10*n, Nothing)
这将输出:
λ> main
10
20
30
42
5
6
7
8
9
999
这看起来是对的。
评论