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

眉间皱痕 提交于 2020-01-24 06:45:07

问题


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?


回答1:


unfoldWhileM is same as your whileM except that it takes an action (not a list) as second argument.

myReadList = unfoldWhileM (/= "") getLine



回答2:


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



回答3:


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


来源:https://stackoverflow.com/questions/47133634/the-haskell-way-to-do-io-loops-without-explicit-recursion

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!