getLine是惰性的吗?

3

getLine是懒惰的吗?

假设我有一个非常长的输入行,它只是一系列数字。我只需要计算前三个数字的总和。 getLine是否会高效地读取并仅读取行的第一部分,还是我必须创建自己的懒惰读取函数,以逐个字符读取?

如果我要对整行求和,我的实现是否高效?(逐个字符读取是否会导致额外的开销?)

import Control.Applicative

main = do
    line <- getLine'
    print $ sum $ map read $ take 3 $ words line

getLine' :: IO String
getLine' = do
    c <- getChar
    if c == '\n' then return [] else (c:) <$> getLine'

我认为库getLine和你的getLine'都同样严格。IO操作不能返回惰性,除非利用一些unsafe函数--这被称为“lazy IO”,必须小心处理,因为由于惰性,实际读取将稍后开始,这可能会导致一些问题。懒惰IO是(臭名昭著的)难以调试的。但是,您可以使用一个严格的自定义get3Ints,它仅读取您需要的字符串部分。 - chi
3
为了确保正确性,getLine必须是严格的,正如Chi所说,你的getLine'行为完全相同。如果getLine不是严格的,则稍后进行的纯计算将导致IO,通过从惰性输入中实现更多字符。当你考虑到其他IO也可以在进行中,也从标准输入读取时,这将是一场噩梦:哪些字符会被放置在何处将非常难以确定。 - amalloy
1
参见这个答案,了解有关IO和惰性的更多信息。如果你想要一个惰性的getLine,它可能需要一个类似于IO (ListT IO Char)的类型,其中data ListT m a = Nil | Cons a (m (ListT m a))(如果你按给定长度的块读取输入以提高效率,也可以使用ListT IO String代替ListT IO Char)。 - gallais
这个世界上,踩赞有什么作用? - Will Ness
1个回答

1

getLine函数不是惰性的,但getContents函数是惰性的,并且可以与lineswords等函数组合使用。因此,以下程序只会读取足够的stdin来获取(最多)第一行中的三个整数并打印它们的总和:

main :: IO ()
main = do contents <- getContents
          let lns = lines contents
              result = sum $ map read $ take 3 $ words $ head lns
          print (result :: Integer)

请注意,如果您修改程序以访问后续行-例如,如果您添加了以下内容:
putStrLn $ take 80 $ lns !! 1

要打印第二行的前80个字符,程序需要读完第一行(因此在程序的最后两行之间会停顿一会儿),然后才能处理第二行的前80个字符。换句话说,如果您仅需要读取第一行的一小部分,则这种懒惰的行读取才有用,如果您没有意识到这一点--Haskell没有任何神奇的方法来跳过第一行的其余部分以到达第二行。

最后,请注意,对于上述程序,如果第一行中的整数少于三个,则它将仅对这些数字求和,并且不会尝试读取超出第一行的内容(我认为这正是您想要的)。如果您实际上不关心行结尾,只想对文件中的前三个数字求和,无论它们如何分成行,那么您可以直接将内容拆分为单词,如下所示:

main = do contents <- getContents
          let result = sum $ map read $ take 3 $ words contents
          print (result :: Integer)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接