在Pipes库中使用请求和响应进行双向通信

在Pipes库中使用请求和响应进行双向通信

本文介绍了在Pipes库中使用请求和响应进行双向通信的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

此问题与Haskell 管道

This question is about the Haskell Pipes library

背景:

>上一个问题中,我问如何使用管道和形成答案的循环我知道是不要这样做.请改用requestresponse".尽管有一个出色且写得很好的教程涵盖了ProducersConsumersPipesEffects用普通英语. requestresponse 文档 >和Server首先定义类别,并提及其他CompSci概念,例如"生成器设计模式."和"迭代设计模式.从来没有解释过.因此,我不知道如何改为使用requestresponse".

In a previous question, I asked how to form a cycle using pipes and the answer I got was "don't do that. Use request and response instead." While there is an excellent and clearly written tutorial that covers Producers, Consumers, Pipes, and Effects in plain English. The documentation for request and response Client and Server starts by defining Categories and mentioning some other CompSci concepts like "the generator design pattern." and "the iteratee design pattern." which are never explained. So I'm stuck not knowing how to "use request and response instead."

设置

我有两个需要反复来回传递数据的状态机,例如robotintCode.

I have two state-machines like thing that need to pass data back and forth repeatedly, robot and 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值,await的两个指令(一个新的颜色和一个转弯),更新了机器人的状态(r),然后重新开始.

It yields a value, awaits two instructions (a new color and a turn), updates the state (r) of the robot, and starts over.

intCode虚拟机已运行程序,可以与机械手进行通信.它需要一个程序(称为code)并创建一条管道,该管道将await从机器人读取数据,然后yield向其发送两条指令.

The intCode virtual machine runs programmed to communicate with the robot. It takes a program (called code) and creates a pipe that will await the sensor reading from the robot then yield two instructions to it.

(boot code) :: Pipe Int Int m ()

让我们假设不容易修改IntCode VM,而对机械手则很容易.

Let's assume that the IntCode VM is not easily modified, but that the robot is.

问题:

requestrespondawaityield有何不同?

如何使用它们促进机器人和VM之间的持续通信?

How do I use them to facilitate continuous communication between the robot and the VM?

推荐答案

awaityield的定义是:

await = request ()
yield = respond

,因此它们与requestrespond密切相关. awaityield版本刚刚专门用于基于单向拉式的流(Producer s,Pipe s和Consumer s).

so they are closely related to request and respond. The await and yield versions have just been specialized to unidirectional pull-based streams (Producers, Pipes and Consumers).

要在两个端点之间执行双向通信,您需要设置一个ClientServer并连接它们.

To perform bidirectional communication between two endpoints, you want to set up a Client and a Server and connect them.

Client是发出请求的单子动作:

A Client is a monadic action that makes requests:

y <- request x

通过发送请求x和接收响应y进行

. Server是响应的单子动作:

by sending request x and receiving response y. A Server is a monadic action that responds:

x <- respond y

通过接受请求x并发送响应y.请注意,这些操作是对称的,因此在给定的应用程序中,Client的一半是Server的一半是任意的.

by accepting request x and sending response y. Note that these operations are symmetric, so in a given application it's arbitrary which half is the Client and which half is the Server.

现在,您可能会注意到,虽然Client发送x并收到y作为响应,但Server似乎是向后的.它在接收请求x之前发送响应y!实际上,它只需要落后一步-基于请求的流中的服务器将希望将其响应y发送到上一个请求,以便接收下一个请求x.

Now, you may notice that while the Client sends an x and receives a y in response, the Server seems backward. It sends response y before receiving request x! In fact, it just needs to operate one step behind -- a server in a pull-based stream will want to send its response y to the previous request in order to receive the next request x.

作为一个简单的示例,下面是一个Client,它要求将数字相加以计算2的幂:

As a simple example, here's a Client that requests addition of numbers to calculate powers of two:

-- |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"

编写服务器添加数字会有些棘手,因为这项业务落后一步".我们可以从以下内容开始:

Writing the server to add numbers is a little trickier because of this "one step behind" business. We might start by writing:

-- |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动作的参数来开始事情:

The trick is to get things started by accepting the first request as an argument to the monadic action:

-- |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

幸运的是,拉点式连接器+>>具有连接这些类型的正确类型:

Fortunately, the pull point-ful connector +>> has the right type to connect these:

mypipe :: Effect IO ()
mypipe = sum2 +>> power2

我们可以按通常的方式运行结果:

and we can run the resulting effect in the usual manner:

main :: IO ()
main = runEffect mypipe

ghci> main
1
2
4
8
16
32
64
128
256
512
1024
"Done"

请注意,对于这种类型的双向通信,请求和响应需要以同步锁定步骤运行,因此您不能等效于产生一次并等待两次.如果您想重新设计上面的示例以分两部分发送请求,则需要开发一种具有明智的请求和响应类型的协议,例如:

Note that, for this type of bidirectional communication, requests and responses need to run in synchronous lock-step, so you can't do the equivalent of yielding once and awaiting twice. If you wanted to re-design the example above to send requests in two parts, you'd need to develop a protocol with sensible request and response types, like:

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
    ...

对于您的大脑/机器人应用程序,您可以将机器人设计为客户端:

For your brain/robot application, you can design the robot as either a client:

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

由于机器人会在消耗输入之前产生输出,因此作为客户端,它将与大脑服务器一起放入基于拉式的流中:

Because the robot produces output before consuming input, as a client it will fit into a pull-based stream with a brain server:

brainS :: Color -> Server Color (Color,Turn) Identity ()
brainS = ...

approach1 = brainS +>> robotC

或作为服务器,它将与具有大脑客户端的基于推送的流一起使用:

or as a server it will fit into a push-based stream with a brain client:

brainC :: Color -> Client (Color,Turn) Color Identity ()
brainC = ...

approach2 = robotS >>~ brainC

这篇关于在Pipes库中使用请求和响应进行双向通信的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-21 10:54