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)
}
hugomg
  • 68,213
  • 24
  • 160
  • 246

4 Answers4

11

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.

Gabriella Gonzalez
  • 34,863
  • 3
  • 77
  • 135
  • Looks like we needed to add an extra level of "lift" to all the previous monadic operations. Can we work around that or is it a fundamental limitation of this sort of transformer-based solution? I understand that unlike a while loop, the `quit` should also work when deeply nested or in other functions but I wouldn't be comfortable adding those lifts it were the only way to go. (And I definitely agree that there is no need to complicate things in the actual tutorial - its just that it happened to be a good example for what I was thinking) – hugomg Jun 24 '13 at 06:03
  • 1
    @missingno My rule of thumb is to only use this solution when the `loop` solution is too cumbersome (I.e. there are a lot of cases that loop and only one case that exits). – Gabriella Gonzalez Jun 24 '13 at 19:44
8

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.

hugomg
  • 68,213
  • 24
  • 160
  • 246
Daniel Fischer
  • 181,706
  • 17
  • 308
  • 431
  • `whileM_` seems to do the same thing but with the parameters in the "usual" order. Thats exactly what I was looking for. – hugomg Jun 23 '13 at 19:34
  • 1
    In a way, `whileM_`'s argument order is more natural. However, that needs the inversion of the test, I thought "do something until EOF" is more natural than "while (not EOF) do something". Personal preference. – Daniel Fischer Jun 23 '13 at 19:36
  • I also just noticed that untilM_ checks the condition after the loop body though. That would have different behaviour than the orifinal code. – hugomg Jun 23 '13 at 19:40
  • 1
    Oh, so much for trusting the analogy of the name. The non-monadic `until` from `base` checks first. – Daniel Fischer Jun 23 '13 at 19:41
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.

ony
  • 12,457
  • 1
  • 33
  • 41
  • Dunno, I feel that this is more of a general control flow question than a pipes-specific one. And there definitely is implicit control flow if we are dealing with monadic code. – hugomg Jun 23 '13 at 19:59
  • @missingno, monads are explicit flow described in terms of language. Most of imperative languages have implicit flow. When you'll unsugar `do` notation you'll see exact combining of monads. Same with many other abstractions that describe some "flow of execution/parsing/generation/calculation/etc". They all described inside of Haskell. While language itself apply not so strict order of execution (lazy is only rule that you can relay on). – ony Jun 23 '13 at 20:14
-1

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

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

AmitSandesara
  • 35
  • 1
  • 7