[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