pipes
有一种使用“推送类别”管道的民间方法。完整的实现来自这个邮件列表帖子和这个 Stack Overflow 答案。我认为它还没有发布,原因是为了简化Pipes
接口,专注于使用通过这个方法隐藏的“排序”monad 实例,并且还没有证据证明这个实现真正正确地实现了 Arrow 类。
这个想法是实现一个Edge
新类型(如下所示),它是一个基于推送的管道,其类型参数以正确的顺序用于Category
、Arrow
和ArrowChoice
两者Functor
以及Applicative
它们的输出值。这使您可以使用箭头符号将它们组合成有向无环图。我将在下面运行实现,但忽略它并使用Arrow
/ ArrowChoice
/Applicative
实例而Edge
不用过多关注它是安全的。
(编辑:此代码最好在https://github.com/Gabriel439/Haskell-RCPL-Library上提供)
{-# 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
因此,在允许运行之前,它们需要至少一个上游值。这意味着需要通过将第一个值作为函数调用传递来“启动”整个过程,并且最左边的 push-Proxy
将控制整个流。
给定一个基于推送的管道,我们可以实现Category
和。标准解决方案还涉及类型类,以便我们以正确的顺序获得类型参数和Arrow
ArrowChoice
Edge
Category
Arrow
newtype Edge m r a b = Edge { unEdge :: a -> Pipe a b m r }
例如Category
,我们使用具有push
asid
和(<~<)
as 组合的“push”类别:
instance Monad m => Category (Edge m r) where
id = Edge push
Edge a . Edge b = Edge (a <~< b)
我们通过在向下边缘增加(ie )将函数嵌入到Edge
with中。为此,我们使用了有规律的类别,但是却把我们塞进了这个过程中。arr
id
push
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)
最后,我们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
PProducer
case
Pipe
f
yield
Pipe
instance Functor (PProducer m r) where
fmap f (Edge k) = $ Edge $ \() -> go (k ()) where
go p = case p of
Request () ku -> Request () (\() -> go (ku ()))
-- This is the only interesting line
Respond b ku -> Respond (f b) (\() -> go (ku ()))
M m -> M (m >>= \p' -> return (go p'))
Pure r -> Pure r
最后,Applicative
除了我们必须在运行上游管道以产生函数和运行下游管道以产生参数之间切换之外,几乎相同。
instance (Monad m) => Applicative (Edge m r ()) where
pure b = Edge $ \() -> forever $ respond b
(Edge k1) <*> (Edge k2) = Edge (\() -> goL (k1 ()) (k2 ()))
where
goL p1 p2 = case p1 of
Request () ku -> Request () (\() -> goL (ku ()) p2)
Respond f ku -> goR f (ku ()) p2
M m -> M (m >>= \p1' -> return (goL p1' p2))
Pure r -> Pure r
goR f p1 p2 = case p2 of
Request () ku -> Request () (\() -> goR f p1 (ku ()))
Respond x ku -> Respond (f x) (\() -> goL p1 (ku ()))
M m -> M (m >>= \p2' -> return (goR f p1 p2'))
Pure r -> Pure r