2013-06-23 6 views
6

The current version of the Pipes tutorial, verwendet die folgenden zwei Funktionen in einem des Beispiels:Wie kann ich in Haskell aus einer Schleife ausbrechen?

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 

Wie im tutorial buchbar! Selbst, P.stdin etwas komplizierter ist aufgrund der Notwendigkeit, das Ende der Eingabe zu überprüfen .

Gibt es irgendwelche schönen Möglichkeiten, um P.stdin umschreiben zu können, um keine manuelle Tail-rekursive Schleife zu benötigen und Steuerfluss-Kombinatoren höherer Ordnung zu verwenden, wie es Pstdout tut? In einer imperativen Sprache würde ich eine strukturierte verwenden while-Schleife oder eine break-Anweisung, das Gleiche zu tun:

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

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

Antwort

9

wie ein Job Sucht whileM_:

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

oder, do-Notation ähnlich das ursprüngliche Beispiel:

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

das monad-loops Paket bietet whileM auch die eine Liste von Zwischenergebnissen statt igno zurück klingeln Sie die Ergebnisse der wiederholten Aktion und andere nützliche Kombinatoren.

+0

'whileM_' scheint das Gleiche zu tun, aber mit den Parametern in der" üblichen "Reihenfolge. Genau das habe ich gesucht. – hugomg

+1

In gewisser Weise ist 'whileM_' die Argumentreihenfolge natürlicher. Allerdings benötigt das die Umkehrung des Tests, ich dachte "tue etwas bis EOF" ist natürlicher als "während (nicht EOF) etwas tun". Persönliche Präferenz. –

+0

Ich habe auch gerade bemerkt, dass bis M_ die Bedingung nach dem Schleifenkörper überprüft. Das hätte ein anderes Verhalten als der ursprüngliche Code. – hugomg

1

Da es keinen impliziten Fluss gibt, gibt es keine solche "Pause". Außerdem ist Ihr Beispiel bereits ein kleiner Block, der in einem komplizierteren Code verwendet wird.

Wenn Sie aufhören wollen "Strings herzustellen", sollte es von Ihrer Abstraktion unterstützt werden. I.e. einige "Managment" von "Pfeifen" mit speziellen Monaden in Consumer und/oder andere Monaden, die mit diesem verbunden sind.

+0

Weiß nicht, ich denke, dass dies eher eine allgemeine Kontrollflussfrage als eine Pipes-spezifische ist. Und es gibt definitiv einen impliziten Kontrollfluss, wenn es sich um monadischen Code handelt. – hugomg

+0

@missingno, Monaden sind explizite Strömung in Bezug auf die Sprache beschrieben.Die meisten Imperativsprachen haben einen impliziten Ablauf. Wenn Sie die Do-Notation nicht verwenden, sehen Sie eine exakte Kombination von Monaden. Gleiches gilt für viele andere Abstraktionen, die einen "Ablauf der Ausführung/Analyse/Generierung/Berechnung/etc" beschreiben. Sie alle beschrieben in Haskell. Während Sprache selbst nicht so strenge Reihenfolge der Ausführung anwenden (faul ist nur Regel, die Sie weiterleiten können). – ony

10

ziehe ich folgendes:

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 

Sie verwenden es wie folgt aus:

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 

this blog post Sehen Sie, wo ich diese Technik erklären.

Der einzige Grund, warum ich das nicht im Tutorial verwende, ist, dass ich es für weniger anfängerfreundlich halte.

+0

Sieht so aus, als müssten wir alle vorherigen monadischen Operationen um einen zusätzlichen "Lift" erhöhen. Können wir das umgehen oder ist es eine grundlegende Einschränkung dieser Art von transformatorbasierter Lösung? Ich verstehe, dass im Gegensatz zu einer while-Schleife das 'quit' auch funktionieren sollte, wenn es tief verschachtelt ist oder in anderen Funktionen, aber ich würde nicht bequem sein, diese Anhebungen hinzuzufügen, es war der einzige Weg zu gehen. (Und ich stimme definitiv zu, dass es keine Notwendigkeit gibt, die Dinge im eigentlichen Tutorial zu komplizieren - es ist einfach ein gutes Beispiel für das, was ich dachte) – hugomg

+1

@missingno Meine Faustregel ist, diese Lösung nur zu verwenden, wenn ' Loop-Lösung ist zu umständlich (dh es gibt viele Fälle, die Schleife und nur einen Fall, der beendet). –

0

Sie können einfach Import System.exit, und verwenden Sie exitWith ExitSuccess

Eg. if (input == 'q') dann exitWith ExitSuccess sonst drucken 5 (alles)