2013-06-20 9 views
6

Dire che ho questo codice:Come posso avere una pipe con più tipi di comunicazione?

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 

Attualmente il client invia sempre Int, e riceve Bool. Tuttavia, voglio che il client sia in grado di interrogare il valore più alto che il server abbia visto finora. Quindi ho anche bisogno di comunicare di inviare () e di ricevere Int. Potrei codificarlo come client che invia Either Int() e riceve Either Bool Int. Tuttavia, mi piacerebbe assicurarmi che i due non siano misti - l'invio di un Int ottiene sempre una risposta Bool.

Come si può fare?

+0

Perché non dovrebbero o lavoro? Se si invia una Either al server, è possibile eseguire la corrispondenza del modello nelle caselle Left o Right e inviare un appropriato Left o Right back. – Dwilson

+3

@Dwilson: Vuole garantire staticamente che l'invio di 'Int' restituisca sempre un' Bool'. L'uso di "Entrambi" sposterebbe questo controllo in runtime. –

risposta

5

Ogni volta che si desidera che una pipeline abbia due interfacce separate, è necessario nidificare il trasformatore monad Proxy all'interno di se stesso. Ciò significa che si desidera che il tipo:

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

Dati i seguenti due clienti per ogni interfaccia:

client1 :: (Monad m, Proxy p) =>() -> Client p Int Bool m r 
client2 :: (Monad m, Proxy p) =>() -> Client p() Int m r 

cui li collegarsi alle due interfacce server utilizzando:

oneInterface() = runProxy (twoInterfaces() >-> client1) 

main = runProxy (client1 >-> oneInterface) 

Per ulteriori maggiori informazioni su questo trucco, leggi la sezione Branching, zips, and merges del tutorial corrente.

Si può anche fare il contrario. È possibile avere un Client con due interfacce separate e collegare due diversi Server s. Questo potrebbe o meno adattarsi meglio al tuo problema.

Si noti che questo sarà molto più semplice in pipes-4.0.0 (attualmente su Github), dove i tipi saranno molto più conciso e non avrà bisogno di 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 
+0

Ho dovuto modificare un secondo snippet (pipe 4.0.0) per compilare, espandendo l'interno 'Server() Intm' a' Proxy xy() Int m', perché (a mio parere) non c'è modo di avere un sinonimo di tipo parzialmente applicato con GHC – ajp

+1

@ajp È anche possibile utilizzare 'Server'' per farlo funzionare. Ogni sinonimo di tipo polimorfico ha uno concreto che non richiede di essere completamente applicato a tutti i parametri di tipo. –

1

Quello che potresti fare è utilizzare due pipeline, su misura per i tuoi due casi d'uso. Un server restituirebbe uno Bool, l'altro restituirebbe un Int. Un client accetta uno Bool, l'altro accetta uno Int. I due client sarebbero in realtà Pipe s. Uno restituirebbe uno Left Int, l'altro restituirebbe uno Right Bool (o viceversa). I due server possono essere passati in un IORef o qualcosa del genere. Potresti quindi usarlo per tenere traccia del valore massimo. A Pipe prendendo Either Int Bool alla fine dei due client potrebbe essere utilizzato per fare qualcosa con entrambi i casi Left e Right restituiti dai client.