问题描述
此问题与Haskell 管道库
This question is about the Haskell Pipes library
背景:
在>上一个问题中,我问如何使用管道和形成答案的循环我知道是不要这样做.请改用request
和response
".尽管有一个出色且写得很好的教程涵盖了Producers
,Consumers
,Pipes
和Effects
用普通英语. request
和response
文档 >和Server
首先定义类别,并提及其他CompSci概念,例如"生成器设计模式."和"迭代设计模式.从来没有解释过.因此,我不知道如何改为使用request
和response
".
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."
设置
我有两个需要反复来回传递数据的状态机,例如robot
和intCode
.
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 yield
s a value, await
s 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.
问题:
request
和respond
与await
和yield
有何不同?
如何使用它们促进机器人和VM之间的持续通信?
How do I use them to facilitate continuous communication between the robot and the VM?
推荐答案
await
和yield
的定义是:
await = request ()
yield = respond
,因此它们与request
和respond
密切相关. await
和yield
版本刚刚专门用于基于单向拉式的流(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 (Producer
s, Pipe
s and Consumer
s).
要在两个端点之间执行双向通信,您需要设置一个Client
和Server
并连接它们.
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库中使用请求和响应进行双向通信的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!