[Haskell-beginners] lazy mapM

Ovidiu D ovidiudeac at gmail.com
Wed Apr 3 11:09:29 CEST 2013


OK. Thanks for the help and see you around!


On Wed, Apr 3, 2013 at 4:50 AM, David McBride <toad3k at gmail.com> wrote:

> Actually, I'm not sure there is.  I can make a few minor improvements, but
> that's about it.  When chaining conduits together, they are kind of weird,
> but a general way that I string them together would be like this:
>
>
> runProcessPipe =
>   conduitSource readCommand
>     $= conduitTakeWhile ( "exit" /=)
>     $= CL.map processCommand
>     $$ CL.mapM_ (putStr . unlines)
>
> Where each source combines with a conduit to make a new source, which
> combines with next conduit, etc and then the last sink uses a $$ to finish
> it off.  But other than that I think you have a handle on it.
>
>
> On Tue, Apr 2, 2013 at 8:39 PM, Ovidiu D <ovidiudeac at gmail.com> wrote:
>
>> Trying to understand the conduits and looking for "the clean way to do
>> it" I got to the code below (based on David's conduit example).
>>
>> I'm quite happy with the result. My problem is that I had to write
>> functions like conduitTakeWhile and conduitSource (see them at the bottom
>> of the code). Are there any functions like this in the conduit library
>> which I'm missing somehow? Is there a way to emulate this behaviour with
>> the existing functions from conduit package?
>>
>>
>>
>> import Data.Conduit
>> import Data.Conduit.List as L
>> import System.IO
>> import Control.Monad.Trans
>>
>>
>> main = do
>>   hSetBuffering stdout NoBuffering
>>   runProcessPipe
>>
>> runProcessPipe =
>>   conduitSource readCommand
>>   $= conduitTakeWhile ( "exit" /=)
>>   =$= L.map processCommand
>>   $$ L.mapM_ $ putStr.unlines
>>
>> readCommand :: IO (Maybe String)
>> readCommand = do
>>     putStr ">> "
>>     isEof <- hIsEOF stdin
>>     if isEof
>>         then return Nothing
>>         else getLine >>= return . Just
>>
>> processCommand cmd = ["reversed string:",reverse cmd]
>>
>> -- Utilities
>>
>> conduitSource :: Monad m => (m (Maybe a)) -> Source m a
>> conduitSource f = do
>>     v <- lift f
>>
>>     case v of
>>         Nothing -> return ()
>>         Just x -> yield x >> conduitSource f
>>
>> conduitTakeWhile :: Monad m => ( a -> Bool ) -> Conduit a m a
>> conduitTakeWhile p = do
>>     cmd <- await
>>     case cmd of
>>         Nothing -> return ()
>>         Just v -> do
>>             if p v
>>                 then yield v >> conduitTakeWhile p
>>                 else return ()
>>
>>
>>
>> On Wed, Apr 3, 2013 at 12:05 AM, Ovidiu D <ovidiudeac at gmail.com> wrote:
>>
>>> I managed to compile and it works but I don't full understand all the
>>> type details. I'll have to dig into Pipes and Conduits.
>>>
>>> Thanks a lot for the code!
>>>
>>>
>>>
>>> On Mon, Apr 1, 2013 at 5:51 AM, David McBride <toad3k at gmail.com> wrote:
>>>
>>>> I'm sorry I jacked up the code editing my email inline, the pipes
>>>> section below main should look like this:
>>>>
>>>> commandProducer :: Producer String IO ()
>>>>
>>>> commandProducer = do
>>>>   x <- lift getLine
>>>>   if x == "exit"
>>>>     then return ()
>>>>     else P.yield x >> commandProducer
>>>>
>>>> displayConsumer :: PrintfArg a => Consumer a IO ()
>>>>
>>>> displayConsumer = forever $ P.await >>= lift . printf "Command not
>>>> implemented (pipes): '%s'\n"
>>>>
>>>>
>>>>
>>>> On Sun, Mar 31, 2013 at 10:49 PM, David McBride <toad3k at gmail.com>wrote:
>>>>
>>>>> 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
>>>>>>
>>>>>>
>>>>>
>>>>
>>>> _______________________________________________
>>>> 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/20130403/76ef0242/attachment.htm>


More information about the Beginners mailing list