【问题标题】:Difficulty with file I/O not producing output文件 I/O 难以产生输出
【发布时间】:2012-03-17 21:18:34
【问题描述】:

我刚刚学习 Haskell,我正在尝试编写一些代码来简单地读取文件并使用 lines 函数创建行列表。例如,我有一个名为 data.txt 的文件,其中包含以下几行:

this is line one
another line
and the final line

这是我试图用来将这些数据读入列表并将其打印到屏幕上的代码:

import System.IO  
import Control.Monad

main = do  
        let list = []
        handle <- openFile "data.txt" ReadMode
        contents <- hGetContents handle
        let myLines = lines contents
            list = listLines myLines
        print list
        hClose handle   

listLines :: [String] -> [String]
listLines = map read

生成的代码编译,但不产生任何输出。我得到以下输出:

runhaskell test.hs        
read_file.hs: Prelude.read: no parse

谁能帮我理解我的代码有什么问题?谢谢。

【问题讨论】:

  • 你希望listLines 做什么?
  • 问题是,String → String 版本的 read 需要 Haskell 字符串格式(即像 "Hello \\" world \\"" 这样的原始字符串,然后将其转换为 Hello " world ")。如果您只想打印从文件中获得的内容,请完全删除 list = listLines myLines
  • 我希望listLines 获取一个字符串列表并返回一个字符串列表。嗯,我真的不明白原始字符串与我的代码有什么关系?你能进一步解释一下吗?
  • 你和this very similar question是同一个人吗?
  • read 应该采用某种数据类型的字符串表示并将其转换为该类型的值。因此,例如read "42" :: Int ≡ 42read "[1,2]" :: [Int] ≡ [1,2]。什么是字符串的表示形式,已经是字符串?好吧,您使用字符串(例如hello world),添加引号,以转义形式显示特殊字符(即换行符→\n"\" 等等)。您希望您的 Haskell 代码以完全 this 的形式转换为 read 字符串;但由于显而易见的原因,这失败了。

标签: haskell


【解决方案1】:

您可能已经注意到,错误消息告诉您read 有问题,所以让我们专注于此。

正如我在 cmets 中所说,read 采用某种数据类型的值的字符串表示,尝试解析它并返回该值。一些例子:

read "3.14"    :: Double ≡ 3.14    :: Double
read "'a'"     :: Char   ≡ 'a'     :: Char
read "[1,2,3]" :: [Int]  ≡ [1,2,3] :: [Int]

一些非示例:

read "[1,2," :: [Int] ≡ error "*** Exception: Prelude.read: no parse"
read "abc"   :: Int   ≡ error "*** Exception: Prelude.read: no parse"

当您尝试使用readString 版本(即read :: String → String)时会发生什么?

Haskell 对 String 的表示(例如,当您评估在 GHCi 中返回 String 的东西时)由包含在 " ... " 引号中的一系列字符组成。当然,如果你想显示一些特殊字符(比如换行符),你必须把转义的版本放在那里(在这种情况下是\n)。

还记得我写read 需要一个值的字符串表示吗?在您的情况下,read 期望 完全 这种字符串格式。自然,它尝试做的第一件事就是匹配开场白。由于您的第一行" 开头,read 会抱怨并使程序崩溃。

read "hello" :: String 失败的方式与read "1" :: [Int] 失败的方式相同; 1 单独不能被解析为 Ints 的列表 - read 期望字符串以左括号 [ 开头。


您可能还听说过show,它是read 的反义词(但在非常 松散的意义上)。根据经验,如果你想read 一个值x,字符串表示read 期望看起来像show x


如果您要将文件的内容更改为以下内容

"this is line one"
"another line"
"and the final line"

您的代码可以正常工作并产生以下输入:

["this is line one","another line","and the final line"]

如果您不想更改您的.txt 文件,只需删除list = listLines myLines 并执行print myLines。但是,当你运行程序时,你会得到

["this is line one","another line","and the final line"]

再次。那么有什么问题呢?

print = putStrLn ∘ showshow 的默认行为当涉及到 showing 列表时(即 [a] 用于某些 aChar 除外,它得到特殊处理)是产生字符串[ firstElement , secondElement ... lastElement ]。如您所见,如果您想避免[ ... ],则必须将[String] 重新合并在一起。

有一个漂亮的函数叫做unlines,它是lines的反函数。另请注意,print 首先调用show,但在这种情况下我们不希望这样(我们已经得到了我们想要的字符串)!所以我们使用putStrLn 就完成了。最终版本:

main = do  
    handle <- openFile "data.txt" ReadMode
    contents <- hGetContents handle
    let myLines = lines contents
    putStrLn (unlines myLines)
    hClose handle

我们也可以去掉不需要的 lines ~ unlines 和只是 putStrLn contents

【讨论】:

  • 哦,所以你有一些期待,这可以写成单行:readFile "data.txt" &gt;&gt;= putStrLn
【解决方案2】:

hGetContents handle 将句柄的内容读入字符串。要将所述字符串转换为行列表,因此您确实希望将其拆分为换行符。你不需要处理read——你只需要字符串。所以你只需要lines :: String -&gt; [String]。此外,您的代码似乎表明您正在“强制思考”,通过在阅读之前“初始化”list。尝试类似

main = do 
  handle <- openFile "data.txt" ReadMode
  contents <- hGetContents handle 
  let list = lines contents
  print list
  hClose handle

不过,有一些方法可以让您的程序更简单。如果您实际上不需要该文件的句柄来处理其他内容,您只需调用readFile :: FilePath -&gt; IO String 即可轻松获取文件中的所有文本。如果您不需要 list 实际绑定到一个名称,您也可以摆脱它,将您的程序减少到

main = do
  contents <- readFile "data.txt"
  print (lines contents)

回顾一下这里发生的事情:读取“data.txt”,其全部内容以contents :: String 提供。您想将此字符串转换为字符串列表,每行一个,即 lines contents 的确切含义。

【讨论】:

  • 请注意,这并不能解决他在代码中遇到的问题。
  • 哦,对不起,我一定是看错了。希望他还能从中提取一些有用的东西,所以我会留下它。 (编辑:是的,我知道这是关于误解read。哦,好吧......如果这是常见的礼仪,我会删除答案)
  • 没有必要这样做。你那里有很多有用的东西。 Haskellers 是好人,他们不会因此而对你投反对票(我希望):)
  • 是的,请不要删除。这很有帮助!
猜你喜欢
  • 1970-01-01
  • 2018-02-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-01-17
  • 2012-05-01
  • 2010-12-25
  • 2015-08-20
相关资源
最近更新 更多