Haskell Processing [IO String]

I have the following function:

lines' :: [IO String] lines' = getLine : lines' 

I was hoping that I could use all the powerful functions of the list on this list, for example, a filter, etc. But my knowledge of the IO monad in haskell is upgradeable.

The concept of list-of-io_stuff convinced me after using Rx for C #.

Is there a way to do what I want in haskell? Sort of:

 ten_lines :: [IO String] ten_lines = take 10 lines' proc_lines :: [IO String] proc_lines = [ (l, length l) | l <- lines' ] 

Thanks!

+4
source share
2 answers

Tikhon's solution is the simplest, but it has one major drawback: it will not produce any results until the entire list is processed, and it will overflow if you process a list that is too large.

A solution closer to C # Rx would be to use a stream library like pipes .

For example, you can define a Producer that generates a String from user input:

 import Control.Monad import Control.Proxy lines' :: (Proxy p) => () -> Producer p String IO r lines' () = runIdentityP $ forever $ do str <- lift getLine respond str 

Then you can define a step that takes 10 lines:

 take' :: (Monad m, Proxy p) => Int -> () -> Pipe paam () take' n () = runIdentityP $ replicateM_ n $ do a <- request () respond a 

... and then the processing step:

 proc :: (Monad m, Proxy p) => () -> Pipe p String (String, Int) mr proc () = runIdentityP $ forever $ do str <- request () respond (str, length str) 

... and the final output stage:

 print' :: (Proxy p, Show a) => () -> Consumer pa IO r print' () = runIdentityP $ forever $ do a <- request () lift $ print a 

Now you can link them into a processing chain and run it:

 main = runProxy $ lines' >-> take' 10 >-> proc >-> print' 

... and it immediately gives the processed result after entering each line, and does not give the result as a batch at the end:

 $ ./pipes Apple<Enter> ("Apple",5) Test<Enter> ("Test",4) 123<Enter> ("123",3) 4<Enter> ("4",1) 5<Enter> ("5",1) 6<Enter> ("6",1) 7<Enter> ("7",1) 8<Enter> ("8",1) 9<Enter> ("9",1) 10<Enter> ("10",2) $ 

In practice, you do not need to define these channels yourself. You can assemble the same chain of components in the standard pipes library:

 >>> runProxy $ stdinS >-> takeB_ 10 >-> mapD (\x -> (x, length x)) >-> printD <exact same behavior> 
+8
source

There is a whole group of normal list functions modified to work with monads in Control.Monad . Of particular interest to your question:

 sequence :: Monad m => [ma] -> m [a] mapM :: Monad m => (a -> mb) -> [a] -> m [b] filterM :: Monad m => (a -> m Bool) -> [a] -> m [a] foldM :: Monad m => (a -> b -> ma) -> a -> [b] -> ma 

( sequence and mapM actually exported by the prelude and are available by default.)

For example, consider the type of your example, take 10 lines' :

 Prelude Control.Monad> :t take 10 lines' take 10 lines' :: [IO String] 

We want to turn this [IO String] into one IO [String] action. This is exactly what sequence does! We can say this type signature. So:

 sequence $ take 10 lines' 

will do what you want.

Most of these functions also have a version ending in _ , for example sequence_ . This has the same effect as a normal function, except that it discards the result, returning () instead. That is, sequence_ :: [ma] -> m () . This is a good choice when you really do not care about the results for two reasons: it describes your intentions in more detail, and productivity can be better.

So, if you want to print 10 lines and not get them, you should write something like this:

 printLines = putStrLn "foo" : printLines main = sequence_ $ take 10 printLines 
+10
source

Source: https://habr.com/ru/post/1479504/


All Articles