Reputation: 2041
I want to read a list of strings seperated by newlines from STDIN, until a new line is witnessed and I want an action of the type IO [String]
. Here is how I would do it with recursion:
myReadList :: IO String
myReadList = go []
where
go :: [String] -> IO [String]
go l = do {
inp <- getLine;
if (inp == "") then
return l;
else go (inp:l);
}
However, this method of using go obscures readability and is a pattern so common that one would ideally want to abstract this out.
So, this was my attempt:
whileM :: (Monad m) => (a -> Bool) -> [m a] -> m [a]
whileM p [] = return []
whileM p (x:xs) = do
s <- x
if p s
then do
l <- whileM p xs
return (s:l)
else
return []
myReadList :: IO [String]
myReadList = whileM (/= "") (repeat getLine)
I am guessing there is some default implementation of this whileM
or something similar already. However I cannot find it.
Could someone point out what is the most natural and elegant way to deal with this problem?
Upvotes: 4
Views: 482
Reputation: 27771
A solution using the effectful streams of the streaming package:
import Streaming
import qualified Streaming.Prelude as S
main :: IO ()
main = do
result <- S.toList_ . S.takeWhile (/="") . S.repeatM $ getLine
print result
A solution that shows prompts, keeping them separated from the reading actions:
main :: IO ()
main = do
result <- S.toList_
$ S.zipWith (\_ s -> s)
(S.repeatM $ putStrLn "Write something: ")
(S.takeWhile (/="") . S.repeatM $ getLine)
print result
Upvotes: 1
Reputation: 26161
Yes for abstracting out the explicit recursion as mentioned in the previous answer there is the Control.Monad.Loop
library which is useful. For those who are interested here is a nice tutorial on Monad Loops.
However there is another way. Previously, struggling with this job and knowing that Haskell is by default Lazy i first tried;
(sequence . repeat $ getLine) >>= return . takeWhile (/="q")
I expected the above to collect entered lines into an IO [String]
type. Nah... It runs indefinitely and IO actişons don't look lazy at all. At this point System IO Lazy
might come handy too. It's a 2 function only simple library.
run :: T a -> IO a
interleave :: IO a -> T a
So run
takes an Lazy IO action and turns it into an IO action and interleave
does the opposite. Accordingly if we rephrase the above function as;
import qualified System.IO.Lazy as LIO
gls = LIO.run (sequence . repeat $ LIO.interleave getLine) >>= return . takeWhile (/="q")
Prelude> gls >>= return . sum . fmap (read :: String -> Int)
1
2
3
4
q
10
Upvotes: 1
Reputation: 758
unfoldWhileM is same as your whileM
except that it takes an action (not a list) as second argument.
myReadList = unfoldWhileM (/= "") getLine
Upvotes: 13