9

The current version of the Pipes tutorial, uses the following two functions in one of the example:

 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

As is mentinoed in the tutorial itself, P.stdin is a bit more complicated due to the need to check for the end of input.

Are there any nice ways to rewrite P.stdin to not need a manual tail recursive loop and use higher order control flow combinators like P.stdout does? In an imperative language I would use a structured while loop or a break statement to do the same thing:

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

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

4 回答 4

12

I prefer the following:

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

You use it like this:

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

See this blog post where I explain this technique.

The only reason I don't use that in the tutorial is that I consider it less beginner-friendly.

于 2013-06-24T02:31:00.070 回答
9

Looks like a job for whileM_:

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

or, using do-notation similarly to the original example:

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

The monad-loops package offers also whileM which returns a list of intermediate results instead of ignoring the results of the repeated action, and other useful combinators.

于 2013-06-23T19:26:30.093 回答
1

Since there is no implicit flow there is no such thing like "break". Moreover your sample already is small block which will be used in more complicated code.

If you want to stop "producing strings" it should be supported by your abstraction. I.e. some "managment" of "pipes" using special monad in Consumer and/or other monads that related with this one.

于 2013-06-23T19:31:23.160 回答
0

You can simply import System.Exit, and use exitWith ExitSuccess

Eg. if (input == 'q') then exitWith ExitSuccess else print 5 (anything)

于 2014-11-24T04:26:04.420 回答