管道展开组成

时间:2017-03-11 19:25:43

标签: haskell haskell-pipes

我目前正在学习管道。在玩双向管道时,我注意到展开的构图看起来非常相似:

(//>) :: Monad m => Proxy x' x b' b m r -> (b -> Proxy x' x c' c m b') -> Proxy x' x c' c m r
-- instead of:
(//>) :: Monad m => Proxy x' x b' b m r -> (b -> Proxy b' b c' c m b') -> Proxy x' x c' c m r

但我们必须分享x'和x类型,因为我们wire up延续的方式:

(>>=) :: Monad m => Proxy a' a b' b m r -> (r -> Proxy a' a b' b m r') -> Proxy a' a b' b m r'
case p0 of
    Request x' fx  -> Request x' (\x -> go (fx x))
    Respond b  fb' -> fb b >>= \b' -> (fb' b')
    ...

但这很容易解决:

import Pipes
import Pipes.Core hiding ((//>))

main :: IO ()
main = runEffect $ lhs //> rhs

infixl 4 //>
(//>) :: Monad m => Proxy x' x b' b m r -> (b -> Proxy b' b c' c m b') -> Proxy x' x c' c m r
p //> f = p >>~ go
    where go x = go =<< request =<< f x

lhs :: Proxy x' x String String IO ()
lhs = each [1..10::Int] //> \i -> do
    r <- respond $ "response nr. " ++ show i
    lift . putStrLn $ "lhs: " ++ show r

rhs :: String -> Proxy String String  x x' IO String
rhs x = do
    lift . putStrLn $ "rhs 1: " ++ show x
    y <- request "yield manually to upstream!"
    lift . putStrLn $ "rhs 2: " ++ show y
    return "return to upstream"

预期输出:

rhs 1: "response nr. 1"
lhs: "yield manually to upstream!"
rhs 2: "response nr. 2"
lhs: "return to upstream"
rhs 1: "response nr. 3"
lhs: "yield manually to upstream!"
rhs 2: "response nr. 4"
lhs: "return to upstream"
rhs 1: "response nr. 5"
lhs: "yield manually to upstream!"
rhs 2: "response nr. 6"
lhs: "return to upstream"
rhs 1: "response nr. 7"
lhs: "yield manually to upstream!"
rhs 2: "response nr. 8"
lhs: "return to upstream"
rhs 1: "response nr. 9"
lhs: "yield manually to upstream!"
rhs 2: "response nr. 10"
lhs: "return to upstream"

最好的我可以说这也不违反任何法律。

所以最后我的问题是:为什么Pipes使用当前的定义?

1 个答案:

答案 0 :(得分:1)

我相信(//>)'s contract的相关部分是......

  

(p //> f)respond替换p中的每个f

...这意味着f将以相同的方式处理从p收到的所有值。然而,这正是你的组合者所规避的 - 在你的例子中,当你浏览each [1..10]的元素时,你在消息集之间交替。为了进一步说明这一点,这里是您的代码的略微修改版本(特别是,我为您的组合器选择了一个不同的名称,并在(//>)之后立即使用普通的each [1..10],因为您的组合行为同样的情况):

infixl 4 //>*
(//>*) :: Monad m =>
    Proxy x' x b' b m r -> (b -> Proxy b' b c' c m b') -> Proxy x' x c' c m r
p //>* f = p >>~ go
    where go x = f x >>= request >>= go

src :: Monad m => Producer Int m ()
src = each [1..10]

-- The types of lhs and rhs are more restrictive than yours, but for this
-- usage pattern (and with the adjustments I made) that is not a problem. 
lhs :: Show a => a -> Server String String IO ()
lhs = \i -> do
    r <- respond $ "response nr. " ++ show i
    lift . putStrLn $ "lhs: " ++ r

rhs :: String -> Client String String IO String
rhs x = do
    lift . putStrLn $ "rhs 0: Will this happen for every value?"
    lift . putStrLn $ "rhs 1: " ++ x
    y <- request "yield manually to upstream!"
    lift . putStrLn $ "rhs 2: " ++ y
    return "return to upstream"

我在rhs ...

开始时插入的问题的答案
GHCi> runEffect $ (src //> lhs) //>* rhs
rhs 0: Will this happen for every value?
rhs 1: response nr. 1
lhs: yield manually to upstream!
rhs 2: response nr. 2
lhs: return to upstream
rhs 0: Will this happen for every value?
rhs 1: response nr. 3
lhs: yield manually to upstream!
rhs 2: response nr. 4
lhs: return to upstream
rhs 0: Will this happen for every value?
rhs 1: response nr. 5
lhs: yield manually to upstream!
rhs 2: response nr. 6
lhs: return to upstream
rhs 0: Will this happen for every value?
rhs 1: response nr. 7
lhs: yield manually to upstream!
rhs 2: response nr. 8
lhs: return to upstream
rhs 0: Will this happen for every value?
rhs 1: response nr. 9
lhs: yield manually to upstream!
rhs 2: response nr. 10
lhs: return to upstream

......不是。与使用(//>)作为最外面的组合子连接函数时发生的情况形成对比,如下所示:

GHCi> runEffect $ src //> (\x -> lhs x //>* rhs)
rhs 0: Will this happen for every value?
rhs 1: response nr. 1
lhs: yield manually to upstream!
rhs 0: Will this happen for every value?
rhs 1: response nr. 2
lhs: yield manually to upstream!
rhs 0: Will this happen for every value?
rhs 1: response nr. 3
lhs: yield manually to upstream!
rhs 0: Will this happen for every value?
rhs 1: response nr. 4
lhs: yield manually to upstream!
rhs 0: Will this happen for every value?
rhs 1: response nr. 5
lhs: yield manually to upstream!
rhs 0: Will this happen for every value?
rhs 1: response nr. 6
lhs: yield manually to upstream!
rhs 0: Will this happen for every value?
rhs 1: response nr. 7
lhs: yield manually to upstream!
rhs 0: Will this happen for every value?
rhs 1: response nr. 8
lhs: yield manually to upstream!
rhs 0: Will this happen for every value?
rhs 1: response nr. 9
lhs: yield manually to upstream!
rhs 0: Will this happen for every value?
rhs 1: response nr. 10
lhs: yield manually to upstream!

这里的每个值都会产生一个单一响应服务器,而不是设置最多可提供十个响应的服务器(src //> lhs),其响应由rhs客户端处理。鉴于没有第二个响应可以从服务器中获取,rhs之后的request中的代码永远不会运行。因此,src的值均匀处理。为了进一步强调这一点,请注意使用组合器来实现这一点是不必要的:src //> (lhs >~> void . rhs)做同样的事情。

(另一件需要注意的事情是,如果我们将lhsrhs的类型更改为您最初的类型,我们可以将上面的管道编写为{{1但是,这与src //>* (\x -> lhs x //>* rhs)不同。这是一个关联性失败,所以你的组合子不会产生一个类别。)

它还有助于澄清用(src //>* lhs) //>* rhs替换你的组合器的情况(我确定你已经在测试中试过了):

(>>~)

GHCi> runEffect $ (src //> lhs) >>~ void . rhs rhs 0: Will this happen for every value? rhs 1: response nr. 1 lhs: yield manually to upstream! rhs 2: response nr. 2 提供最多10个回复;但是,src //> lhs仅发出两个请求,因此其他八个响应未被使用。对我来说,这表明你的组合器最好被表达为一种让客户无限期地继续请求的方式:

rhs
-- requestForever :: Monad m => (b -> Client b' b m b') -> b -> Client b' b m r
requestForever :: Monad m => 
    (b -> Proxy b' b c' c m b') -> b -> Proxy b' b c' c m r
requestForever f = go
    where go x = f x >>= request >>= go