Question

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?

Was it helpful?

Solution

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

OTHER TIPS

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

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top