Say I have this code:
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
Currently the client always sends Int
, and receives Bool
. However, I want the client to also be able to query for the highest value that the server has seen so far. So I also need communication of sending ()
and receiving Int
. I could encode this as the client sending Either Int ()
, and receiving Either Bool Int
. However, I'd like to ensure that the two aren't mixed - sending an Int
always gets a Bool
response.
How can this be done?
Any time you want a pipeline to have two separate interfaces you have to nest the Proxy
monad transformer within itself. That means you want the type:
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
...
Given the following two clients for each interface:
client1 :: (Monad m, Proxy p) => () -> Client p Int Bool m r
client2 :: (Monad m, Proxy p) => () -> Client p () Int m r
You would connect them to the two server interfaces using:
oneInterface () = runProxy (twoInterfaces () >-> client1)
main = runProxy (client1 >-> oneInterface)
To learn more about this trick, read the Branching, zips, and merges section of the current tutorial.
You can also do it the other way around, too. You can have a Client
with two separate interface and hook up two different Server
s. This may or may not fit your problem better.
Note that this will get much simpler in pipes-4.0.0
(currently on Github), where the types will be much more concise and you won't need 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
What you could do is use two pipelines, tailored to your two use cases. One server would return a Bool
, the other would return an Int
. One client would accept a Bool
, the other would accept an Int
. The two clients would actually be Pipe
s. One would return a Left Int
, the other would return a Right Bool
(or vice versa). The two servers could be passed in an IORef
or something of the sort. You could then use this to keep track of the maximum value. A Pipe
taking Either Int Bool
at the end of the two clients could be used to do something with both the Left
and Right
cases returned by the clients.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With