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