我正在使用pipes-4.0.0。在该库中,管道下游yields的数量通常与上游awaits的数量无关。
但是假设我想构建一个限制管道,强制执行每个await只执行一个yield,同时仍然能够使用monadic(>> =)对这些管道进行排序。
我观察到,在双向情况下,代理上游的每个值requested都与发回的值匹配。因此,我正在寻找的是类型Proxy a' a () b m r -> Pipe a (Either b a') m r
的函数,它“反映”上游的值,将它们转化为下游的额外收益。或者,不太常见,Client a' a -> Pipe a a'
。这样的功能可能吗?
答案 0 :(得分:4)
你绝对不想使用pipes
。但是,可以做的是定义执行此操作的受限类型,在该受限类型中执行所有连接和逻辑,然后在完成后将其提升为Pipe
。
您想要的类型是这个,类似于netwire
Wire
:
{-# LANGUAGE DeriveFunctor #-}
import Control.Monad.Trans.Free -- from the 'free' package
data WireF a b x = Pass (a -> (b, x)) deriving (Functor)
type Wire a b = FreeT (WireF a b)
这是一个monad和monad变换器,因为它是用FreeT
实现的。然后你可以实现这个方便的操作:
pass :: (Monad m) => (a -> b) -> Wire a b m ()
pass f = liftF $ Pass (\a -> (f a, ()))
...并使用monadic语法组装自定义连线:
example :: Wire Int Int IO ()
example = do
pass (+ 1)
lift $ putStrLn "Hi!"
pass (* 2)
然后,当您完成使用此受限Wire
类型的内容后,您可以将其宣传为Pipe
:
promote :: (Monad m) => Wire a b m r -> Pipe a b m r
promote w = do
x <- lift $ runFreeT w
case x of
Pure r -> return r
Free (Pass f) -> do
a <- await
let (b, w') = f a
yield b
promote w'
请注意,您可以定义标识和电线和电线组合:
idWire :: (Monad m) => Wire a a m r
idWire = forever $ pass id
(>+>) :: (Monad m) => Wire a b m r -> Wire b c m r -> Wire a c m r
w1 >+> w2 = FreeT $ do
x <- runFreeT w2
case x of
Pure r -> return (Pure r)
Free (Pass f2) -> do
y <- runFreeT w1
case y of
Pure r -> return (Pure r)
Free (Pass f1) -> return $ Free $ Pass $ \a ->
let (b, w1') = f1 a
(c, w2') = f2 b
in (c, w1' >+> w2')
我很确定这些形成Category
:
idWire >+> w = w
w >+> idWire = w
(w1 >+> w2) >+> w3 = w1 >+> (w2 >+> w3)
另外,我很确定promote
遵守以下仿函数法律:
promote idWire = cat
promote (w1 >+> w2) = promote w1 >-> promote w2
答案 1 :(得分:0)
我的直觉是,如果不是完全不可能的话,这将很难做到。您不仅可以编写充满复杂循环的生产者和消费者,而且monadic接口也可以说消费者的控制流可以取决于从生产者那里获得的价值。
consumer = do
n <- await
for i in 1..n do
m <- await
print m
很难对生产者的类型进行编码,“这会生成N + 1个数字,其中N是第一个数字的值”。
回到主题,我想如果你使用自己的组合器代替管道的基础monadic接口,你可能会有更好的机会。例如,boomerang web routes库使用一组组合器来同时构建执行(Route - &gt; URL)转换的代码和执行(URL - &gt; Route)转换的代码,从而保证它们相互兼容,互相反转。