Haskell 执行 IO 循环的方法(没有显式递归)?
The Haskell way to do IO Loops (without explicit recursion)?
我想从 STDIN 读取由换行符分隔的字符串列表,直到出现新行并且我想要 IO [String]
类型的操作。以下是我将如何使用递归来实现:
myReadList :: IO String
myReadList = go []
where
go :: [String] -> IO [String]
go l = do {
inp <- getLine;
if (inp == "") then
return l;
else go (inp:l);
}
但是,这种使用 go 的方法模糊了可读性,并且是一种非常常见的模式,理想情况下人们希望将其抽象出来。
所以,这是我的尝试:
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)
我猜这个 whileM
或类似的东西已经有了一些默认实现。但是我找不到它。
有人能指出处理这个问题最自然、最优雅的方法是什么吗?
unfoldWhileM 与您的 whileM
相同,只是它需要一个动作(不是列表)作为第二个参数。
myReadList = unfoldWhileM (/= "") getLine
是的,为了抽象出上一个答案中提到的显式递归,有 Control.Monad.Loop
library which is useful. For those who are interested here is a nice tutorial on Monad Loops.
不过还有一个办法。以前,在这项工作中苦苦挣扎,并且知道 Haskell 默认情况下是 Lazy 我第一次尝试;
(sequence . repeat $ getLine) >>= return . takeWhile (/="q")
我希望上面的代码将输入的行收集到 IO [String]
类型中。不……它会无限期地运行,而且 IO actişons 看起来一点也不懒惰。此时 System IO Lazy
也可能派上用场。这是一个只有 2 个功能的简单库。
run :: T a -> IO a
interleave :: IO a -> T a
所以 run
采取一个惰性 IO 操作并将其变成一个 IO 操作,而 interleave
做相反的事情。因此,如果我们将上述函数改写为;
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
使用 streaming 包的有效流的解决方案:
import Streaming
import qualified Streaming.Prelude as S
main :: IO ()
main = do
result <- S.toList_ . S.takeWhile (/="") . S.repeatM $ getLine
print result
显示提示的解决方案,将它们与阅读操作分开:
main :: IO ()
main = do
result <- S.toList_
$ S.zipWith (\_ s -> s)
(S.repeatM $ putStrLn "Write something: ")
(S.takeWhile (/="") . S.repeatM $ getLine)
print result
我想从 STDIN 读取由换行符分隔的字符串列表,直到出现新行并且我想要 IO [String]
类型的操作。以下是我将如何使用递归来实现:
myReadList :: IO String
myReadList = go []
where
go :: [String] -> IO [String]
go l = do {
inp <- getLine;
if (inp == "") then
return l;
else go (inp:l);
}
但是,这种使用 go 的方法模糊了可读性,并且是一种非常常见的模式,理想情况下人们希望将其抽象出来。
所以,这是我的尝试:
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)
我猜这个 whileM
或类似的东西已经有了一些默认实现。但是我找不到它。
有人能指出处理这个问题最自然、最优雅的方法是什么吗?
unfoldWhileM 与您的 whileM
相同,只是它需要一个动作(不是列表)作为第二个参数。
myReadList = unfoldWhileM (/= "") getLine
是的,为了抽象出上一个答案中提到的显式递归,有 Control.Monad.Loop
library which is useful. For those who are interested here is a nice tutorial on Monad Loops.
不过还有一个办法。以前,在这项工作中苦苦挣扎,并且知道 Haskell 默认情况下是 Lazy 我第一次尝试;
(sequence . repeat $ getLine) >>= return . takeWhile (/="q")
我希望上面的代码将输入的行收集到 IO [String]
类型中。不……它会无限期地运行,而且 IO actişons 看起来一点也不懒惰。此时 System IO Lazy
也可能派上用场。这是一个只有 2 个功能的简单库。
run :: T a -> IO a
interleave :: IO a -> T a
所以 run
采取一个惰性 IO 操作并将其变成一个 IO 操作,而 interleave
做相反的事情。因此,如果我们将上述函数改写为;
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
使用 streaming 包的有效流的解决方案:
import Streaming
import qualified Streaming.Prelude as S
main :: IO ()
main = do
result <- S.toList_ . S.takeWhile (/="") . S.repeatM $ getLine
print result
显示提示的解决方案,将它们与阅读操作分开:
main :: IO ()
main = do
result <- S.toList_
$ S.zipWith (\_ s -> s)
(S.repeatM $ putStrLn "Write something: ")
(S.takeWhile (/="") . S.repeatM $ getLine)
print result