Haskell文件读取

52

我最近刚开始学习 Haskell ,但是我很难弄清楚文件读取的工作原理。

例如,我有一个包含数字行的文本文件 "test.txt":

32 4
2 30
300 5

我想逐行读取并评估每个单词,并将它们相加。

因此,我正在尝试做这样的事情:

import System.IO
import Control.Monad

main = do
        let list = []
        handle <- openFile "test.txt" ReadMode
        contents <- hGetContents handle
        singlewords <- (words contents)
        list <- f singlewords
        print list
        hClose handle

f :: [String] -> [Int]
f = map read

我知道这完全是错的,但我完全不知道如何正确使用语法。

非常感谢任何帮助以及提供有例子和代码解释的好教程链接,但除了这个已经阅读完毕。

3个回答

88

不错的开始!唯一需要记住的是,纯函数应用应该使用let,而不是绑定符号<-

import System.IO  
import Control.Monad

main = do  
        let list = []
        handle <- openFile "test.txt" ReadMode
        contents <- hGetContents handle
        let singlewords = words contents
            list = f singlewords
        print list
        hClose handle   

f :: [String] -> [Int]
f = map read
这是让这个程序编译并运行所需的最小更改。从编码风格上讲,我有一些评论:
  1. 两次绑定 list 看起来有点可疑。请注意,这不会改变 list 的值--它只是遮盖了旧定义。
  2. 尽量使用内联纯函数!
  3. 可能的话,使用 readFile 优于手动打开、读取和关闭文件。
实现这些更改后的结果如下:
main = do  
        contents <- readFile "test.txt"
        print . map readInt . words $ contents
-- alternately, main = print . map readInt . words =<< readFile "test.txt"

readInt :: String -> Int
readInt = read

哦,哇,谢谢 :) 但是我并不需要打印列表,实际上我想把它保留为一个列表,因为我要将每行相加,并得到总和,但还是非常感谢您的帮助! - DustBunny

15

Daniel Wagner的解决方案非常棒。这里是另一种尝试,以便您可以获得更多有关高效文件处理的想法。

{-#  LANGUAGE OverloadedStrings #-}
import System.IO
import qualified Data.ByteString.Lazy.Char8 as B
import Control.Applicative
import Data.List

sumNums :: B.ByteString -> Int
sumNums s = foldl' sumStrs 0 $ B.split ' ' s

sumStrs :: Int -> B.ByteString -> Int
sumStrs m i = m+int
              where Just(int,_) = B.readInt i

main = do 
  sums <- map sumNums <$> B.lines <$> B.readFile "testy"
  print sums

首先,您会看到OverloadedStrings编译指令。这允许我们使用普通引号来表示实际上是字节字符串的字符串文字。我们将使用惰性字节字符串处理文件,原因有几个。首先,它允许我们通过程序流式传输文件,而不是一次性强制将所有内容读入内存。此外,字节字符串通常比字符串更快且更有效。

其他部分基本上都很简单。我们通过readFile将文件读入懒惰列表中的行,然后映射一个求和函数到每个行上。 <$>只是一种快捷方式,允许我们在IO()函数值内操作 -- 如果这太麻烦了,我很抱歉。我指的是当您读取文件时,您不会得到一个ByteString,而是一个包装在IO中的ByteString,即IO(ByteString)。<$>意味着“嗨,我想对IO内部的东西进行操作,然后再将其重新包装起来。

B.split根据空格将每行分隔成数字。(我们也可以使用B.words)唯一其他有趣的部分是在sumStrs中,我们使用解构/模式匹配从readInt函数返回的Just中提取第一个值。

希望这有帮助。如果您有任何问题,请随时提问。


3
谢谢!您的方法有很多我不熟悉的语法,但是我会在有机会扩展我的Haskell知识时参考它。(我刚开始学) :) - DustBunny

1

对于所有非函数式编程人员,这是一份福利。

unsafePerformIO . readFile $ "file.txt"

将文件读取到字符串中

不是IO String,只是一个普通的完全加载好的字符串,可以直接使用。这可能不是正确的方法,但它可行,而且无需更改现有函数以适应IO String。

p.s. 别忘了导入

import System.IO.Unsafe 

3
谢谢。使得在GHCi上进行实验变得更加容易了。 - Daniel C. Sobral
7
“不需要改变你现有的函数来适应IO String”,其实也没有必要这样做。无论如何,任何纯函数都应该保持纯净,如果你想在IO中使用它,只需使用fmap(或带有x <- someIOActiondo块)将其提升到该单子中即可。 - leftaroundabout
22
是的,因为unsafe不应该引起任何警报 :) 这是一个坏主意。在GHCi中,您可以使用s <- readFile "file.txt"来获取s中的内容,无需使用unsafe*函数。 - Mihai Maruseac
8
我认为对于学习 Haskell 的人来说,介绍 unsafePerformIO 是非常糟糕的建议。我使用 Haskell 已经超过十年了,从未见过在普通程序(即非系统级别)中使用 unsafePerformIO。初学者应该先掌握 Haskell 的核心内容,然后再去尝试使用 unsafePerformIO - user855443
5
我同意,我从未声称这是一个好的解决方案,而且我同意你应该从核心部分开始。当我写这个时,我还在学习 Haskell,我需要完成这个任务,而这个解决方案对我有效。readfile 返回 IO 类型,而我想要一个正确加载的字符串。我本应该做得更好,但我没有。现在我正在帮助一些人,他们应该用正确的方式来做,但他们不想。但是对于任何想要获得一些赞的人,只需留下另一个评论,提醒人们这是非常糟糕和邪恶的事情。 - Kapytanhook
显示剩余2条评论

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