简体   繁体   中英

The Haskell way to do IO Loops (without explicit recursion)?

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?

unfoldWhileM与你的whileM相同,只是它将一个动作(不是列表)作为第二个参数。

myReadList = unfoldWhileM (/= "") getLine

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

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM