簡體   English   中英

在 Pipes 庫中使用請求和響應進行雙向通信

[英]Using request and response in with the Pipes library for bidirectional communication

這個問題是關於 Haskell Pipes庫的

背景:

在上一個問題中,我詢問了如何使用管道形成循環, 我得到的答案是“不要那樣做。改用requestresponse 。” 雖然有一個優秀且編寫清晰的教程,用簡單的英語涵蓋了ProducersConsumersPipesEffects requestresponse ClientServer文檔首先定義類別並提到一些其他 CompSci 概念,如“ 生成器設計模式”和“ 迭代設計模式” 從來沒有解釋過。 所以我不知道如何“使用requestresponse ”。

設置

我有兩個狀態機,比如需要反復來回傳遞數據的東西, robotintCode

機器人非常簡單:

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 不容易修改,但機器人是。

問題:

requestrespondawaityield什么不同?

我如何使用它們來促進機器人和虛擬機之間的持續通信?

awaityield的定義是:

await = request ()
yield = respond

所以它們與requestrespond密切相關。 awaityield版本剛剛專門用於單向基於拉取的流( 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM