Haskell:将文件中的每一行插入到列表中

11

我目前正在使用Haskell进行项目开发,遇到了一些问题。我需要读取并将“dictionary.txt”文件中的每一行插入到一个列表中,但似乎无法实现。这是我的代码:

main = do
    let list = []
    loadNums "dictionary.txt" list

loadNums location list = do
    inh <- openFile location ReadMode
    mainloop inh list
    hClose inh

mainloop inh list = do 
    ineof <- hIsEOF inh
    if ineof
        then return ()
        else do 
            inpStr <- hGetLine inh
            inpStr:list
            mainloop inh list

它应该获取每一行(我知道它确实获取了每一行,因为将“inpStr:list”替换为“putStrLn inpStr”可以正确地显示所有行),并将其插入到列表中,但我收到以下错误:

Couldn't match expected type `IO' against inferred type `[]'

可能是因为hGetLine不是String类型,而是IO String类型,我不知道如何处理才能得到一个适当的字符串,以便将其插入到我的列表中。我不知道这个问题怎么解决,或者具体是什么问题,但如果有人知道如何将文件中的每一行正确地放入列表中,我会非常感激。

提前感谢!

2个回答

15

除非这是为了作业或某些事情,否则没有理由使用这么多的精力。重复使用才是懒惰!

getLines = liftM lines . readFile

main = do
    list <- getLines "dictionary.txt"
    mapM_ putStrLn list

但由于您似乎仍在学习Haskell,重要的是让您理解CesarB所写的内容。


当向似乎还在学习 Haskell 的人解释事情时,我会避免使用(甚至展示)无意义的风格。不想吓到他们;-) - CesarB
7
+1 提到readFile。使用readFilegetContentsinteract等函数来处理文件读取/行读取/EOF检测/文件关闭等混乱情况,有助于摆脱命令式思维方式。 - Nefrubyr
“mapM_” 是什么意思?我知道 “mapM” 将一个函数映射到单子(monad)上,但带下划线的呢? - Andriy Drozdyuk
@drozzy:mapM_mapM完全相同,只是它忽略了结果。因此,你只需要在单子中使用()而不是获取单子结果的列表。由于putStrLn无论如何都会提供IO (),因此使用mapM将得到IO [()];而mapM_则将其转换为实际所需的IO () - Tikhon Jelvis
我知道已经有一段时间了,但我刚刚看到它。我也是Haskell的初学者,有一个问题。使用您的代码调用main函数只会将dictionary.txt的内容打印到屏幕上。如何将它们放入列表中,以便我能够对该列表执行其他操作? main函数的类型为:: IO()。如何获得一个字符串列表,其中每个字符串都是dictionary.txt的一行?谢谢。 - Max
@Max 这里定义的 getLines :: String -> IO [String] 是可以的。 - ephemient

14

在出错的那一行,Haskell期望的是"IO a"类型,但你给它的是"[]"类型。简单来说,在IO模型中的do块里,每一行都是以下三种情况之一:

  • 返回“IO a”类型值的某些内容;其中“a”类型的值会被丢弃(因此“a”通常为“()”)
  • 一个"<-"表达式,其作用与前者相同,但不是丢弃“a”类型的值,而是将其赋予"<-"左侧的名称
  • 一个let语句,只是为一个值分配一个名称而已

在这个do块中,“hGetLine inh”返回“IO String”,并且从中提取了字符串,并将其命名为inpStr。由于下一行既不是let语句也不是"<-"表达式,它应该有一个“IO a”类型,但它没有(因此导致编译器错误)。相反地,您可以使用let语句,因为您已经有了字符串:

let list' = inpStr:list

这将创建一个由原始列表后跟字符串组成的新列表,并将其命名为"list'"。

将以下行更改为使用"list'"而不是"list"(从而传递给它新列表)。该行调用(递归)mainloop,该函数将读取一行,调用自身,以此类推。在读取整个文件之后,它将返回带有"I0 ()"类型的内容。这个"I0 ()"将返回到loadNums中的do块。恭喜你,你刚刚创建了一个列表,其中包含从文件中读取的行,以相反的顺序(因为您正在将其追加到列表头部),然后什么也没有做。

如果你想对它做点什么,将 "return ()" 更改为 "return list";返回将生成一个 "IO [String]" 类型值,其中包含列表(返回仅封装值),可以通过 <- 语法在loadNums中提取。

其余部分留给读者自己思考。


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