如何正确使用双向管道?
How to use bidirectional pipes properly?
我正在尝试使用管道对双向 Proxy
实例最理想的问题进行建模。基本上,我有类似以下架构的东西:
api logic
| ^
| |
v |
A A'
layer 1
B B'
| ^
| |
v |
layer 2
所以基本上,我有 layer 1
这是一个双向变压器。该模型是基于拉动的,因此我将消息流转换由 logic
组件的拉动触发。
所以我应该 layer1 :: Proxy A' A B' B m x
,想法是 layer1
从 api
中提取 A
,做一些转换 A -> B
然后使用 B'
来自 layer2,应用 B' -> A'
并将其传递给 logic
。
不清楚的是:我知道如何 request
一个 A
并响应一个 B
但我如何从 [=19 生成 A'
=]?库中似乎没有适合这里的组合器...
您需要注意三种类型:Client
s 可以 request
但永远不会 respond
,Server
s 可以 respond
但永远不会request
和 Proxy
都可以。
request
/respond
的参数是要发送的值,绑定的结果分别是response/request。这对于 request
(您绑定响应)具有直观意义,但我花了一点时间才让它点击 respond
(您绑定 next 要求)。它使您的处理步骤整洁的小递归函数。 (我最初的直觉是使用 Control.Monad.forever
,它适用于单向管道,但在这里是错误的工具。)
令人困惑的一点:因为管道本身是同步的,所以您需要获得一个初始值来传递并启动。要么将其传递给 request
(制作一个由 (>~>)
组成的拉式管道),要么将其传递给 respond
(制作一个由 (>+>)
组合的推式管道) .然后将初始值传递到组合管道中,为您提供可以转到 runEffect
.
的 Effect m r
我在下面的示例中使用了拉式管道,因为它符合您的 API-请求隐喻。它实现了这个三级双向管道:
+--------+ Yoken +-----------+ Token +--------+
| |<-------| |<-------| |
| server | | transform | | client |
| |------->| |------->| |
+--------+ String +-----------+ String +--------+
(shouty)
client
生成请求 Token
s 并打印出响应。 transform
将 Token
转换为 Yoken
(嘿,键彼此紧挨着)并将它们向上游传递。它还通过大写和附加 !
将响应变成喊叫。 server
接收 Yoken
s 并生成请求的 yo
s 数量。
import Data.Char
import Control.Monad
import Control.Monad.IO.Class
import Pipes.Core
import System.IO
data Token = Token Int
data Yoken = Yoken Int
main :: IO ()
main = do
hSetBuffering stdout NoBuffering
-- We have to get an initial n outside the pipeline to kick things off.
putStr "N? "
n <- read <$> getLine
runEffect $ server >+> transform >+> client $ Token n
-- The "server" generates a string of "yo"s based on the number inside the Yoken
server :: Monad m => Yoken -> Server Yoken String m a
server (Yoken n) = (respond . concat $ replicate n "yo") >>= server
-- A processing step just for the sake of having one, turn the Token into a
-- Yoken, upcase the string, and append a "!".
transform :: Monad m => Token -> Proxy Yoken String Token String m a
transform (Token t) = do
resp <- request $ Yoken t
next <- respond $ map toUpper resp ++ "!"
transform next
-- Clients request "yo"s, by sending `Token`s upstream.
client :: Token -> Client Token String IO a
client t = do
resp <- request t
n <- liftIO $ putStrLn resp *> putStr "N? " *> fmap read getLine
client $ Token n
我正在尝试使用管道对双向 Proxy
实例最理想的问题进行建模。基本上,我有类似以下架构的东西:
api logic
| ^
| |
v |
A A'
layer 1
B B'
| ^
| |
v |
layer 2
所以基本上,我有 layer 1
这是一个双向变压器。该模型是基于拉动的,因此我将消息流转换由 logic
组件的拉动触发。
所以我应该 layer1 :: Proxy A' A B' B m x
,想法是 layer1
从 api
中提取 A
,做一些转换 A -> B
然后使用 B'
来自 layer2,应用 B' -> A'
并将其传递给 logic
。
不清楚的是:我知道如何 request
一个 A
并响应一个 B
但我如何从 [=19 生成 A'
=]?库中似乎没有适合这里的组合器...
您需要注意三种类型:Client
s 可以 request
但永远不会 respond
,Server
s 可以 respond
但永远不会request
和 Proxy
都可以。
request
/respond
的参数是要发送的值,绑定的结果分别是response/request。这对于 request
(您绑定响应)具有直观意义,但我花了一点时间才让它点击 respond
(您绑定 next 要求)。它使您的处理步骤整洁的小递归函数。 (我最初的直觉是使用 Control.Monad.forever
,它适用于单向管道,但在这里是错误的工具。)
令人困惑的一点:因为管道本身是同步的,所以您需要获得一个初始值来传递并启动。要么将其传递给 request
(制作一个由 (>~>)
组成的拉式管道),要么将其传递给 respond
(制作一个由 (>+>)
组合的推式管道) .然后将初始值传递到组合管道中,为您提供可以转到 runEffect
.
Effect m r
我在下面的示例中使用了拉式管道,因为它符合您的 API-请求隐喻。它实现了这个三级双向管道:
+--------+ Yoken +-----------+ Token +--------+
| |<-------| |<-------| |
| server | | transform | | client |
| |------->| |------->| |
+--------+ String +-----------+ String +--------+
(shouty)
client
生成请求 Token
s 并打印出响应。 transform
将 Token
转换为 Yoken
(嘿,键彼此紧挨着)并将它们向上游传递。它还通过大写和附加 !
将响应变成喊叫。 server
接收 Yoken
s 并生成请求的 yo
s 数量。
import Data.Char
import Control.Monad
import Control.Monad.IO.Class
import Pipes.Core
import System.IO
data Token = Token Int
data Yoken = Yoken Int
main :: IO ()
main = do
hSetBuffering stdout NoBuffering
-- We have to get an initial n outside the pipeline to kick things off.
putStr "N? "
n <- read <$> getLine
runEffect $ server >+> transform >+> client $ Token n
-- The "server" generates a string of "yo"s based on the number inside the Yoken
server :: Monad m => Yoken -> Server Yoken String m a
server (Yoken n) = (respond . concat $ replicate n "yo") >>= server
-- A processing step just for the sake of having one, turn the Token into a
-- Yoken, upcase the string, and append a "!".
transform :: Monad m => Token -> Proxy Yoken String Token String m a
transform (Token t) = do
resp <- request $ Yoken t
next <- respond $ map toUpper resp ++ "!"
transform next
-- Clients request "yo"s, by sending `Token`s upstream.
client :: Token -> Client Token String IO a
client t = do
resp <- request t
n <- liftIO $ putStrLn resp *> putStr "N? " *> fmap read getLine
client $ Token n