2013-06-23 16 views
6

Il current version of the Pipes tutorial, utilizza le seguenti due funzioni in uno degli esempi:Come faccio a uscire da un loop in Haskell?

stdout ::() -> Consumer String IO r 
stdout() = forever $ do 
    str <- request() 
    lift $ putStrLn str 

stdin ::() -> Producer String IO() 
stdin() = loop 
    where 
    loop = do 
     eof <- lift $ IO.hIsEOF IO.stdin 
     unless eof $ do 
      str <- lift getLine 
      respond str 
      loop 

Come mentinoed nel tutorial sé, P.stdin è un po 'più complicato a causa della necessità per verificare la fine dell'input .

Ci sono dei buoni modi per riscrivere P.stdin per non aver bisogno di un ciclo ricorsivo di coda manuale e utilizzare combinatori di flusso di controllo di ordine superiore come fa P.stdout? In un linguaggio imperativo userei un strutturata ciclo while o un'istruzione break per fare la stessa cosa:

while(not IO.isEOF(IO.stdin)){ 
    str <- getLine() 
    respond(str) 
} 

forever(){ 
    if(IO.isEOF(IO.stdin)){ break } 
    str <- getLine() 
    respond(str) 
} 

risposta

9

Sembra un lavoro per whileM_:

stdin() = whileM_ (lift . fmap not $ IO.hIsEOF IO.stdin) (lift getLine >>= respond) 

o, usando do-la notazione in modo simile a nell'esempio originale:

stdin() = 
    whileM_ (lift . fmap not $ IO.hIsEOF IO.stdin) $ do 
     str <- lift getLine 
     respond str 

il pacchetto monad-loops offre anche whileM che restituisce un elenco di risultati intermedi anziché Igno suonare i risultati dell'azione ripetuta e altri utili combinatori.

+0

'whileM_' sembra fare la stessa cosa ma con i parametri nell'ordine" solito ". Questo è esattamente quello che stavo cercando. – hugomg

+1

In un certo senso, l'ordine degli argomenti 'whileM_' è più naturale. Tuttavia, ciò richiede l'inversione del test, ho pensato "fare qualcosa fino a EOF" è più naturale di "while (non EOF) fare qualcosa". Preferenza personale. –

+0

Mi sono anche accorto che fino a quando _ controlla la condizione dopo il corpo del ciclo. Questo avrebbe un comportamento diverso rispetto al codice orifinale. – hugomg

1

Poiché non c'è un flusso implicito non esiste una cosa come "pausa". Inoltre il tuo campione è già un piccolo blocco che verrà utilizzato in un codice più complicato.

Se si desidera interrompere "produrre stringhe", dovrebbe essere supportato dalla propria astrazione. Cioè alcune "managment" di "pipe" usando la monade speciale in Consumer e/o altre monadi relative a questa.

+0

Dunno, penso che si tratti più di una domanda di flusso di controllo generale che di una specifica per tubi. E c'è sicuramente un flusso di controllo implicito se abbiamo a che fare con il codice monadico. – hugomg

+0

@missingno, le monadi sono un flusso esplicito descritto in termini di lingua.La maggior parte delle lingue imperative ha un flusso implicito. Quando annoterai la notazione 'do' vedrai la combinazione esatta delle monadi. Lo stesso con molte altre astrazioni che descrivono alcuni "flusso di esecuzione/analisi/generazione/calcolo/ecc.". Hanno tutti descritto all'interno di Haskell. Mentre il linguaggio stesso non applica un ordine di esecuzione così rigoroso (il pigro è solo la regola che puoi trasmettere). – ony

10

preferisco il seguente:

import Control.Monad 
import Control.Monad.Trans.Either 

loop :: (Monad m) => EitherT e m a -> m e 
loop = liftM (either id id) . runEitherT . forever 

-- I'd prefer 'break', but that's in the Prelude 
quit :: (Monad m) => e -> EitherT e m r 
quit = left 

Si utilizza in questo modo:

import Pipes 
import qualified System.IO as IO 

stdin ::() -> Producer String IO() 
stdin() = loop $ do 
    eof <- lift $ lift $ IO.hIsEOF IO.stdin 
    if eof 
    then quit() 
    else do 
     str <- lift $ lift getLine 
     lift $ respond str 

Vedi this blog post in cui spiego questa tecnica.

L'unica ragione per cui non lo uso nel tutorial è che lo considero meno adatto ai principianti.

+0

Sembra che abbiamo bisogno di aggiungere un ulteriore livello di "sollevamento" a tutte le precedenti operazioni monadiche. Possiamo aggirare il problema o è una limitazione fondamentale di questo tipo di soluzione basata su trasformatori? Capisco che a differenza di un ciclo while, il 'quit' dovrebbe funzionare anche quando è profondamente annidato o in altre funzioni, ma non sarebbe comodo aggiungere quegli ascensori che erano l'unica strada da percorrere. (E sono assolutamente d'accordo sul fatto che non ci sia bisogno di complicare le cose nel tutorial reale - è solo che è stato un buon esempio per quello che stavo pensando) – hugomg

+1

@missingno La mia regola generale è usare questa soluzione solo quando il ' la soluzione loop è troppo macchinosa (cioè ci sono molti casi in cui il ciclo e un solo caso che esce). –

0

si può semplicemente importazione System.exit, e utilizzare exitWith ExitSuccess

Eg. if (input == 'q') quindi exitWith ExitSuccess else print 5 (qualsiasi)