[Haskell-beginners] lazy mapM

David McBride toad3k at gmail.com
Mon Apr 1 04:49:12 CEST 2013


Doing it the way you are trying to do it breaks the IO abstraction.  In
order to do it you'd have to use unsafe functions.  Unsafe functions are
bad.  I'm not going to explain why but they tend to bite you as your
program gets more complex and weirdness starts to occur, like threads
ceasing operation while awaiting input is something that bit me when I went
down that route.  So let me explain how I would do it using both pipes and
conduits as examples:

import Data.Conduit as C hiding ((>+>), runPipe)
import System.IO
import Control.Monad.Trans
import Text.Printf.Mauke

import Control.Pipe as P
import Control.Monad (forever)

-- Source runs in the IO monad and produces Strings
commandSource :: Source IO String
commandSource = do
  command <- liftIO getLine
  if command == "exit"
    then return ()
    else do
      C.yield command
      commandSource -- loop to fetching new values to send down the pipe

-- Sink runs in the IO monad and takes any printfable argument and returns
() when pipe completes.
displaySink :: PrintfArg a => Sink a IO ()
displaySink = do
  m <- C.await
  case m of
    Nothing -> return ()  -- if nothing comes in, just exit
    Just x -> do
      liftIO $ printf "Command not implemented (conduit): '%s'\n" x
      displaySink

main = do
  hSetBuffering stdout NoBuffering
  commandSource $$ displaySink
  runPipe $ commandProducer >+> displayConsumer


commandProducer :: PrintfArg a => Producer a String IO ()
commandProducer = do
  x <- lift getLine
  if x == "exit"
    then return ()
    else P.yield x >> commandProducer

displayConsumer :: Consumer String IO ()
displayConsumer = forever $ P.await >>= lift . printf "Command not
implemented (pipes): '%s'\n"

There are some utility function to shorten some of these definitions a bit
in conduit.  These two examples are equivalent.  But basically you are
creating a pipeline, the first of which gets commands until it gets an exit
and then sends them down the pipeline (as a string).  The second piece of
the pipe accepts anything that is printfable and prints it.  It will stop
when the upstream stops sending it strings to print.  The point here is
that you have little functions that you can compose together with other
functions and create something bigger where none of the pieces interfere
with each other or break the IO abstraction.

As to which of these libraries you should try?  Conduits is a bit more
straight forward and has a lot more documentation and supporting
libraries.  Pipes is a lot more flexible in that you could send things both
directions along the pipe in the future when you become proficient with the
library.




On Sun, Mar 31, 2013 at 9:38 PM, Ovidiu D <ovidiudeac at gmail.com> wrote:

> I'm not sure I understand what you mean by "I know you have the best
> intentions in writing this, but there are pitfalls.". Anyway, here's the
> code which doesn't work apparently because mapM is waiting for the whole
> list before it goes further.
>
> prompt = ">> "
>
> commands :: [IO String]
> commands = readCommand : commands
>     where readCommand = putStr prompt >> getLine
>
> display :: Show a => [ a ] -> IO ()
> display = mapM_ $ putStr . show
>
> executeCommand :: String -> String
> executeCommand = printf "Command not implemented: '%s'"
>
> processCommands :: [IO String] -> IO [ String ]
> processCommands = mapM processOneCommand
>     where processOneCommand cmd = cmd >>= (return . executeCommand )
>
> main =
>     hSetBuffering stdout NoBuffering
>     >> processCommands commands
>     >>= display
>
> This is just for learning purposes and I'm looking for the "haskell way to
> do it". My intention is to write the function processCommands such that it
> takes the decision to either fetch the next command from the command list
> (i.e. console) or to exit the application.
>
> Regarding your comment "Just know that at some point you should learn to
> use conduits or pipes for a much better approach to modeling things like
> this.". Can you point me to some documentation?
>
> Thanks!
>
>
> On Mon, Apr 1, 2013 at 3:53 AM, David McBride <toad3k at gmail.com> wrote:
>
>> I know you have the best intentions in writing this, but there are
>> pitfalls.  Unexpected things happen when you interleave IO in this manner,
>> but nonetheless, here's how you would do it.
>>
>> myGetLine = do
>>   x <- getLine
>>   if (x == "exit")
>>       then return []
>>       else do
>>         xs <- unsafeInterleaveIO myGetLine
>>         return (x:xs)
>>
>> main = do
>>   x <- myGetLine
>>   print x
>>
>> Just know that at some point you should learn to use conduits or pipes
>> for a much better approach to modeling things like this.
>>
>>
>>
>> On Sun, Mar 31, 2013 at 7:26 PM, Ovidiu D <ovidiudeac at gmail.com> wrote:
>>
>>>  Hi again,
>>>
>>> Given the following code:
>>>
>>> g :: IO String -> IO String
>>>
>>> f :: [IO String] -> IO [ String ]
>>> f = mapM g
>>>
>>> The implementation of f is wrong because I would like to:
>>> 1. Make f behave lazy
>>> Its input list is made of lines read from stdin and I want it to process
>>> lines one by one as they are entered by the user.
>>>
>>> 2. Implement  f such that it stops consuming items from the input list
>>> when the input item meets some condition. For example:
>>> isExit item = ("exit" == item)
>>>
>>> I tried to implement my own custom iteration by recursion but I got
>>> stuck in the combination of IO and list monads.
>>>
>>> Any help is appreciated.
>>>
>>> Thanks!
>>>
>>>
>>> _______________________________________________
>>> Beginners mailing list
>>> Beginners at haskell.org
>>> http://www.haskell.org/mailman/listinfo/beginners
>>>
>>>
>>
>> _______________________________________________
>> Beginners mailing list
>> Beginners at haskell.org
>> http://www.haskell.org/mailman/listinfo/beginners
>>
>>
>
> _______________________________________________
> Beginners mailing list
> Beginners at haskell.org
> http://www.haskell.org/mailman/listinfo/beginners
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://www.haskell.org/pipermail/beginners/attachments/20130331/c6c1c083/attachment-0001.htm>


More information about the Beginners mailing list