我想按照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及其亲戚,大概答案就在那里。但我不太清楚它是如何完成的。
答案 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
但是要小心这种方法:
可能有一种提供ZipConduit
抽象的方法,类似于ZipSource
和ZipSink
,可以更优雅地处理这类问题,但我也没想过得多。
编辑我最终在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 post和this Stack Overflow answer。由于努力简化Pipes
接口,专注于使用通过此方法隐藏的“排序”monad实例,并且尚未证明此实现真正实现,我认为它尚未发布箭头课正确。
我们的想法是实现一个新类型Edge
(如下所示),这是一个基于推送的管道,其类型参数的顺序为Category
,Arrow
,{{1并且ArrowChoice
和Functor
都超过了它们的输出值。这允许您使用箭头符号将它们组合成有向非循环图。我将继续执行下面的实现,但是可以放弃它并使用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
将控制整个流。
鉴于基于推送的管道,我们可以实施Proxy
,Proxy
和Category
。标准解决方案还涉及Arrow
类型类,以便我们按正确的顺序为ArrowChoice
和Edge
Category
对于Arrow
个实例,我们使用“推送”类别,其中newtype Edge m r a b = Edge { unEdge :: a -> Pipe a b m r }
为Category
和push
作为组合:
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传递ArrowChoice
和left
边来传递值的负担。
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