2013-06-20 7 views
6

Say mam ten kod:Jak mogę utworzyć potok z wieloma typami komunikacji?

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 

Obecnie klient zawsze wysyła Int i odbiera Bool. Jednak chcę, aby klient mógł również wyszukiwać najwyższą wartość, jaką serwer widział do tej pory. Potrzebuję również komunikacji wysyłania () i odbierania Int. Mogę zakodować to jako klient wysyłający Either Int() i otrzymujący Either Bool Int. Jednak chciałbym się upewnić, że oba nie są mieszane - wysłanie Int zawsze daje odpowiedź Bool.

Jak można tego dokonać?

+0

Dlaczego nie chciał czy praca? Jeśli wyślesz do serwera, możesz dopasować wzorzec w przypadku lewej lub prawej strony i wysłać odpowiedni lewy lub prawy tył. – Dwilson

+3

@Dwilson: Chce statycznie upewnić się, że wysyłanie 'Int' zawsze zwraca' Bool'. Użycie 'albo" przeniesie to sprawdzanie do środowiska wykonawczego. –

Odpowiedz

5

Za każdym razem, gdy rurociąg ma mieć dwa osobne interfejsy, należy zagnieździć w sobie monadowy transformator Proxy. Oznacza to, że chcesz typ:

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

Biorąc pod uwagę następujące dwa klientów dla każdego interfejsu:

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

by podłączyć je do dwóch interfejsów serwerowych używając:

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

main = runProxy (client1 >-> oneInterface) 

się nauczyć więcej o tej sztuczce, przeczytaj sekcję Branching, zips, and merges bieżącego samouczka.

Możesz też zrobić to na odwrót. Możesz mieć Client z dwoma oddzielnymi interfejsami i podłączyć dwa różne Server s. To może, ale nie musi, lepiej pasować do twojego problemu.

Zauważ, że to będzie o wiele prostsze w pipes-4.0.0 (obecnie na Github), gdzie typy będzie znacznie bardziej zwięzły i nie trzeba będzie 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

Musiałem zmienić drugi fragment (pipes 4.0.0), aby skompilować, poprzez rozszerzenie wewnętrznego 'Server() Int m' na' Proxy xy() Int m', ponieważ (według mojego zrozumienia) nie ma sposobu, aby mają częściowo zastosowany synonim GHC – ajp

+1

@ajp Możesz również użyć 'Server'', aby uruchomić go również. Każdy synonim typu polimorficznego ma konkretny, który nie wymaga pełnego zastosowania do wszystkich parametrów typu. –

1

Co możesz zrobić, to użyć dwóch potoków, dopasowanych do twoich dwóch przypadków użycia. Jeden serwer zwróci wartość Bool, a druga zwróci wartość Int. Jeden klient zaakceptowałby numer Bool, a drugi zaakceptowałby numer Int. Dwóch klientów będzie faktycznie Pipe s. Jeden zwróciłby Left Int, drugi zwróciłby Right Bool (lub odwrotnie). Dwa serwery można przekazać w postaci IORef lub czegoś w tym rodzaju. Możesz wtedy użyć tego do śledzenia maksymalnej wartości. Na końcu obu klientów można użyć klienta, aby wykonać pewne czynności zarówno w przypadku zwracanych przez klientów przypadków o numerach Left, jak i .

Powiązane problemy