说我有这段代码:
import Control.Monad.State hiding (StateT)
import Control.Proxy
server :: (Proxy p, Monad m) => Int -> Server p Int Bool (StateT Int m) ()
server = runIdentityK loop
where loop arg = do
currMax <- lift get
lift $ put $ max currMax arg
nextArg <- respond (even arg)
loop nextArg
client :: (Proxy p, Monad m) => Client p Int Bool m ()
client = runIdentityP loop
where loop = go 1
go i = do
isEven <- request i
go $ if isEven
then i `div` 2
else i * 3 + 1
目前,客户端始终发送Int
,并收到Bool
。但是,我希望客户端也能够查询到目前为止服务器已经看到的最高值。所以我还需要发送()
和接收Int
的通信。我可以将其编码为发送Either Int ()
并接收Either Bool Int
的客户端。但是,我想确保两者不是混合的 - 发送Int
始终会得到Bool
响应。
如何做到这一点?
答案 0 :(得分:5)
每当您希望管道具有两个单独的接口时,您必须将Proxy
monad变换器嵌套在其自身中。这意味着您需要类型:
twoInterfaces
:: (Monad m, Proxy p1, Proxy p2 )
=> () -> Int -> Server p1 Int Bool (Server p2 () Int m) r
twoInterfaces () n = runIdentityP . hoist runIdentityP $ do
x <- respond A -- Use outer interface
y <- lift $ respond B -- Use inner interface
...
为每个接口提供以下两个客户端:
client1 :: (Monad m, Proxy p) => () -> Client p Int Bool m r
client2 :: (Monad m, Proxy p) => () -> Client p () Int m r
您可以使用以下方法将它们连接到两个服务器接口:
oneInterface () = runProxy (twoInterfaces () >-> client1)
main = runProxy (client1 >-> oneInterface)
要了解有关此技巧的更多信息,请阅读当前教程的Branching, zips, and merges部分。
你也可以反过来做。你可以拥有一个带有两个独立界面的Client
,并连接两个不同的Server
。这可能会或可能不会更好地适合您的问题。
请注意,这会在pipes-4.0.0
(目前在Github上)变得更加简单,其中类型将更加简洁,您不会需要runIdentityP
:
twoInterfaces
:: (Monad m) => () -> Int -> Server Int Bool (Server () Int m) r
twoInterface () n = do
x <- respond A
y <- lift $ respond B
...
client1 :: (Monad m) => () -> Client Int Bool m r
client2 :: (Monad m) => () -> Client () Int m r
答案 1 :(得分:1)
您可以使用两个管道,根据您的两个用例量身定制。一台服务器将返回Bool
,另一台服务器将返回Int
。一个客户接受Bool
,另一个接受Int
。这两个客户实际上是Pipe
。一个会返回Left Int
,另一个会返回Right Bool
(反之亦然)。这两个服务器可以在IORef
或类似的东西中传递。然后,您可以使用它来跟踪最大值。在两个客户端结束时Pipe
Either Int Bool
可以用来对客户返回的Left
和Right
个案做一些事情。