[英]Using request and response in with the Pipes library for bidirectional communication
這個問題是關於 Haskell Pipes庫的
背景:
在上一個問題中,我詢問了如何使用管道形成循環, 我得到的答案是“不要那樣做。改用request
和response
。” 雖然有一個優秀且編寫清晰的教程,用簡單的英語涵蓋了Producers
、 Consumers
、 Pipes
和Effects
。 request
和response
Client
和Server
的 文檔首先定義類別並提到一些其他 CompSci 概念,如“ 生成器設計模式”和“ 迭代設計模式” 。 從來沒有解釋過。 所以我不知道如何“使用request
和response
”。
設置
我有兩個狀態機,比如需要反復來回傳遞數據的東西, robot
和intCode
。
機器人非常簡單:
robot :: Pipe Int Int m r -- robot never returns so its return type is polymorphic
robot = go newRobot
where
go r = do
yield $ color r
c <- toColor <$> await
turn <- toTurn <$> await
go $ update c turn r
它yield
sa 值, await
s兩條指令(新顏色和轉彎),更新機器人的狀態 ( r
),然后重新開始。
intCode
虛擬機運行編程為與機器人通信。 它需要一個程序(稱為code
)並創建將一個管await
傳感器從機器人讀取然后yield
兩個指令給它。
(boot code) :: Pipe Int Int m ()
讓我們假設 IntCode VM 不容易修改,但機器人是。
問題:
request
和respond
與await
和yield
什么不同?
我如何使用它們來促進機器人和虛擬機之間的持續通信?
await
和yield
的定義是:
await = request ()
yield = respond
所以它們與request
和respond
密切相關。 await
和yield
版本剛剛專門用於單向基於拉取的流( Producer
s、 Pipe
s 和Consumer
s)。
要在兩個端點之間執行雙向通信,您需要設置一個Client
和一個Server
並連接它們。
Client
是一個發出請求的 monadic action:
y <- request x
通過發送請求x
和接收響應y
。 Server
是一個一元動作,它響應:
x <- respond y
通過接受請求x
並發送響應y
。 請注意,這些操作是對稱的,因此在給定的應用程序中,哪一半是Client
哪一半是Server
是任意的。
現在,您可能會注意到,當Client
發送x
並接收y
作為響應時, Server
似乎落后了。 它在收到請求x
之前發送響應y
! 事實上,它只需要向后一步操作——基於拉取的流中的服務器想要將其響應y
發送到前一個請求,以便接收下一個請求x
。
作為一個簡單的例子,這里有一個Client
請求加法來計算 2 的冪:
-- |Client to generate powers of two
power2 :: Client (Int, Int) Int IO ()
power2 = go 1
where go n | n <= 1024 = do
liftIO $ print n
n' <- request (n,n) -- ask adder to add "n" and "n"
go n'
go n = liftIO $ print "Done"
由於這種“落后一步”的業務,編寫服務器以添加數字有點棘手。 我們可以從寫開始:
-- |Server to sum numbers
sum2 :: Server (Int, Int) Int IO ()
sum2 = do
(n,n) <- respond ??? -- send previous response to get current request
let n' = n+n
??? <- respond n' -- send current reponse to get next request
訣竅是通過接受第一個請求作為 monadic 操作的參數來開始工作:
-- |Server to sum numbers
sum2 :: (Int, Int) -> Server (Int, Int) Int IO ()
sum2 (m, n) = do
(m', n') <- respond (m+n) -- send response to get next request
sum2 (m', n') -- and loop
幸運的是,pull point-ful 連接器+>>
有正確的類型來連接這些:
mypipe :: Effect IO ()
mypipe = sum2 +>> power2
我們可以以通常的方式運行結果效果:
main :: IO ()
main = runEffect mypipe
ghci> main
1
2
4
8
16
32
64
128
256
512
1024
"Done"
請注意,對於這種類型的雙向通信,請求和響應需要在同步鎖步中運行,因此您不能執行一次讓步並等待兩次的等效操作。 如果您想重新設計上面的示例以分兩部分發送請求,您需要開發一個具有合理請求和響應類型的協議,例如:
data Req = First Int | Second Int
data Res = AckFirst | Answer Int
power2 = ...
AckFirst <- request n
Answer n' <- request n
sum2 = ...
First m' <- respond (Answer (m+n))
Second n' <- respond AckFirst
...
對於您的大腦/機器人應用程序,您可以將機器人設計為客戶端:
robotC :: Client Color (Color,Turn) Identity ()
robotC = go newRobot
where
go r = do
(c, turn) <- request (color r)
go $ update c turn r
或服務器:
robotS :: Server (Color,Turn) Color Identity ()
robotS = go newRobot
where
go r = do
(c, turn) <- respond (color r)
go $ update c turn r
因為機器人在消費輸入之前產生輸出,所以作為客戶端,它將適合帶有大腦服務器的基於拉動的流:
brainS :: Color -> Server Color (Color,Turn) Identity ()
brainS = ...
approach1 = brainS +>> robotC
或者作為服務器,它將適合帶有大腦客戶端的基於推送的流:
brainC :: Color -> Client (Color,Turn) Color Identity ()
brainC = ...
approach2 = robotS >>~ brainC
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.