单步导管

时间:2014-02-10 07:54:00

标签: haskell conduit

我想按照ArrowChoice的方式做一些事情,但是conduits。我想等待一个Either值,然后将Left值传递给一个管道,将Right值传递给另一个管道,然后将结果合并回一个Either流。

据推测,这可以通过使内部管道像自动机一样来完成:将管道转换为一个带有参数的函数,并返回产生的monadic列表:

newtype AutomataM i m o = Automata (i -> m (o, Automata i o))

conduitStep :: Conduit i m o -> AutomataM i m [o]

输出列表的原因是Conduit可能为每个输入产生0或更多输出。

我看过ResumableConduit及其亲戚,大概答案就在那里。但我不太清楚它是如何完成的。

2 个答案:

答案 0 :(得分:2)

它与您提供的类型签名不完全相同,但是:

import Data.Conduit
import Data.Conduit.Internal (Pipe (..), ConduitM (..))

newtype Automata i o m r = Automata (m ([o], Either r (i -> Automata i o m r)))

conduitStep :: Monad m => ConduitM i o m r -> Automata i o m r
conduitStep (ConduitM con0) =
    Automata $ go [] id con0
  where
    go _ front (Done r) = return (front [], Left r)
    go ls front (HaveOutput p _ o) = go ls (front . (o:)) p
    go ls front (NeedInput p _) =
        case ls of
            [] -> return (front [], Right $ conduitStep . ConduitM . p)
            l:ls' -> go ls' front (p l)
    go ls front (PipeM mp) = mp >>= go ls front
    go ls front (Leftover p l) = go (l:ls) front p

但是要小心这种方法:

  1. 通过将输出保持为列表,它不是常量内存。
  2. 我们扔掉了终结者。
  3. 可能有一种提供ZipConduit抽象的方法,类似于ZipSourceZipSink,可以更优雅地处理这类问题,但我也没想过得多。


    编辑我最终在conduit-extra 0.1.5中实现了ZipConduit。这是一个使用它的演示,听起来有点像你的情况:

    import           Control.Applicative
    import           Data.Conduit
    import           Data.Conduit.Extra
    import qualified Data.Conduit.List   as CL
    
    conduit1 :: Monad m => Conduit Int m String
    conduit1 = CL.map $ \i -> "conduit1: " ++ show i
    
    conduit2 :: Monad m => Conduit Double m String
    conduit2 = CL.map $ \d -> "conduit2: " ++ show d
    
    conduit :: Monad m => Conduit (Either Int Double) m String
    conduit = getZipConduit $
        ZipConduit (lefts =$= conduit1) *>
        ZipConduit (rights =$= conduit2)
      where
        lefts = CL.mapMaybe (either Just (const Nothing))
        rights = CL.mapMaybe (either (const Nothing) Just)
    
    main :: IO ()
    main = do
        let src = do
                yield $ Left 1
                yield $ Right 2
                yield $ Left 3
                yield $ Right 4
            sink = CL.mapM_ putStrLn
        src $$ conduit =$ sink
    

答案 1 :(得分:1)

有一种民间方法可以使用pipes使用“推式”管道来完成此操作。完整的实施来自this mailing list postthis Stack Overflow answer。由于努力简化Pipes接口,专注于使用通过此方法隐藏的“排序”monad实例,并且尚未证明此实现真正实现,我认为它尚未发布箭头课正确。

我们的想法是实现一个新类型Edge(如下所示),这是一个基于推送的管道,其类型参数的顺序为CategoryArrow,{{1并且ArrowChoiceFunctor都超过了它们的输出值。这允许您使用箭头符号将它们组合成有向非循环图。我将继续执行下面的实现,但是可以放弃它并使用Applicative的{​​{1}} / Arrow / ArrowChoice个实例,而不必担心。

编辑:此代码最好在https://github.com/Gabriel439/Haskell-RCPL-Library处提供)


Applicative

这是一种使用管道的非典型模式,并未在Edge模块中公开;您必须导入{-# LANGUAGE RankNTypes #-} {-# LANGUAGE TypeSynonymInstances #-} import Prelude hiding ((.), id) import Pipes.Core import Pipes.Lift import Control.Monad.Morph import Control.Category import Control.Monad.State.Strict import Control.Arrow 才能使用Pipes。基于推的管道看起来像

Pipes.Core

因此在允许push运行之前它们至少需要一个上游值。这意味着整个过程需要通过将第一个值作为函数调用传递来“启动”,并且最左边的推送 - -- push :: a -> Proxy a' a a' a m r 将控制整个流。

鉴于基于推送的管道,我们可以实施ProxyProxyCategory。标准解决方案还涉及Arrow类型类,以便我们按正确的顺序为ArrowChoiceEdge

提供类型参数
Category

对于Arrow个实例,我们使用“推送”类别,其中newtype Edge m r a b = Edge { unEdge :: a -> Pipe a b m r } Categorypush作为组合:

id

我们通过在向下边缘扩充(<~<)(即instance Monad m => Category (Edge m r) where id = Edge push Edge a . Edge b = Edge (a <~< b) ),将函数嵌入Edge arr id。为此,我们使用具有法律push的{​​{1}}类别,但将respond加入到流程中。

p />/ respond == p

我们还使用本地状态转换器来存储f对的一半,并将其传递到instance Monad m => Arrow (Edge m r) where arr f = Edge (push />/ respond . f)

中的输入管道周围
snd

最后,我们通过实施first获得 first (Edge p) = Edge $ \(b, d) -> evalStateP d $ (up \>\ hoist lift . p />/ dn) b where up () = do (b, d) <- request () lift (put d) return b dn c = do d <- lift get respond (c, d) 个实例。为此,我们分担了使用return或pipe传递ArrowChoiceleft边来传递值的负担。

Left

我们可以使用Right来创建“基于推送”的生产者和消费者

instance (Monad m) => ArrowChoice (Edge m r) where
    left (Edge k) = Edge (bef >=> (up \>\ (k />/ dn)))
      where
          bef x = case x of
              Left b -> return b
              Right d -> do
                  _ <- respond (Right d)
                  x2 <- request ()
                  bef x2
          up () = do
              x <- request ()
              bef x
          dn c = respond (Left c)

然后我们会为Edge提供type PProducer m r b = Edge m r () b type PConsumer m r a = forall b . Edge m r a b Functor个实例。这是基于Applicative的{​​{1}}分析,所以它有点冗长。但基本上,所有发生的事情都是我们将PProducer插入case的{​​{1}}广告位。

Pipe

最后,f大致相同,只是我们必须在运行上游管道以生成函数和运行下游管道以生成参数之间切换。

yield