要递归地写这个,你需要一个函数来调用它自己。你已经有一个函数main,可以调用自己,但你不想多次打开文件,所以最好将打开(和关闭)文件的部分分开使用辅助函数读取行的部分:
main :: IO ()
main = do
file <- openFile "test.txt" ReadMode
processLines
hClose file
现在我们可以编写递归的processLines函数了:
processLines :: Handle -> IO ()
processLines file = do
x <- hGetLine file
putStrLn $ x ++ " has a length of " ++ show (length x)
processLines file
这可行,但它会无条件地调用自身,因此它会一直读取行,直到到达文件末尾并引发异常。我们可以使用函数hIsEOF 来解决这个问题:
processLines :: Handle -> IO ()
processLines file = do
eof <- hIsEOF file
if eof
then return ()
else do
x <- hGetLine file
putStrLn $ x ++ " has a length of " ++ show (length x)
processLines file
因为processLines 只是一个辅助函数并且在main 之外没有合理的用途,大多数Haskell 程序员会使用where 子句或let 定义将它拉入main,并且可能会给出它一个较短的一次性名称,例如 loop 或 process 或 go。带有where 子句的结果是:
main :: IO ()
main = do
file <- openFile "test.txt" ReadMode
process file
hClose file
where
process file = do
eof <- hIsEOF file
if eof
then return ()
else do
x <- hGetLine file
putStrLn $ x ++ " has a length of " ++ show (length x)
process file
使用let 的一个优点是您可以在作用域内使用file 变量定义帮助程序,因此您不必将它作为参数传递:
main :: IO ()
main = do
file <- openFile "test.txt" ReadMode
let loop = do
eof <- hIsEOF file
if eof
then return ()
else do
x <- hGetLine file
putStrLn $ x ++ " has a length of " ++ show (length x)
loop
loop
hClose file
Haskell 提供了更合理的方式以循环形式编写此程序,避免显式递归和定义帮助程序的需要。例如:
-- using `whileM_` from `monad-loops`
main1 :: IO ()
main1 = withFile "test.txt" ReadMode $ \h ->
whileM_ (not <$> hIsEOF h) $ do
x <- hGetLine h
putStrLn $ x ++ " has a length of " ++ show (length x)
或:
-- using `forM_` from `Control.Monad`
main2 :: IO ()
main2 = do
contents <- readFile "test.txt"
let lns = lines contents
forM_ lns $ \x ->
putStrLn $ x ++ " has a length of " ++ show (length x)
然而,这些都不是编写这个程序的好方法。从根本上说,你对一行输入有一个纯粹的转换:
annotate :: String -> String
annotate x = x ++ " has a length of " ++ show (length x)
并且您想将其应用于文件中的所有行,您可以使用单行来完成(诚然在 IO 上使用 fmap Functor):
main = putStr =<< unlines . map annotate . lines <$> readFile "test.txt"
或更明确地说:
main = do
content <- readFile "test.txt"
let lns = lines content
lns' = [annotate l | l <- lns]
putStr $ unlines lns'