使用另一根导管对导管进行部分处理

Partially processing a conduit using another conduit

提问人:Clinton 提问时间:11/13/2023 更新时间:11/14/2023 访问量:48

问:

我希望制作一个具有以下签名的函数(我认为):

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

它基本上做到了以下几点:

  1. 重复从管道中获取 type 的值。asourceConduit
  2. 将函数应用于该值。splitFunca
  3. 将值从bsplitFuncconsumingConduit
  4. IF 返回(即不)返回其返回对的第二部分 THENsplitFuncJust (some conduit)Nothing
    1. “close up”,并得到结果值consumingConduitr
    2. 返回一个带有 的“其余”的导管,但在它前面附加了 Just 中的导管。sourceConduit

我实际上已经取得了接近这个成就(提前为蹩脚的命名道歉)。看这里:

{-# 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”中。我宁愿能够使用管道接收器来使用管道源的第一部分,而不是收集列表中的所有元素并对其进行处理。

我是否可以在这里使用导管水槽而不是 ,如果是这样,我需要进行什么样的调整?我考虑过在循环中将元素推送到接收器中,而不仅仅是附加它们,然后以某种方式获得结果,但我无法很好地处理这些类型。任何帮助都表示赞赏。DListgorunConduitr

Haskell 导管

评论


答:

1赞 K. A. Buhr 11/14/2023 #1

我想你想要这样的东西:

{-# 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。如果从不返回管道,则返回 .loopConduitT a b m (Maybe (ConduitT () a m ())abfsplitFuncJustsplitFuncNothing

现在,我们可以 ,其类型为 。这会将 s 从 中下沉到 中,从 返回前缀 conduit(如果有的话)和从 返回。fuseBoth loop snkConduitT a Void m (Maybe (ConduitT () a m (), r)bloopsnksplitFuncrsnk

最后,我们可以.这将运行整个管道 sourcing s from 并将 s sinking 到 ,直到返回前缀 conduit,此时它将返回:src $$+ fuseBoth loop snkasrcbsnksplitFunc

(SealedConduitT () a m (), (Maybe (ConduitT () a m ()), r))

令人难以置信的是,密封的导管是 剩下的,导管是 返回的“前缀”导管,最后一个是 的返回值。剩下的就是将其粘合到适当的返回值中。srcMaybesplitFuncrsnk

这似乎可以按照以下测试工作:

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

这看起来是对的。