我正在学习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
任何人都可以帮我理解我的代码有什么问题吗?感谢。
答案 0 :(得分:8)
您可能已经注意到,错误消息告诉您read
存在问题,所以让我们关注它。
正如我在评论中所说,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"
当您尝试使用String
版read
(即read :: String → String
)时会发生什么?
Haskell对String
的表示(例如,当您评估GHCi中返回String
的内容时)由" ... "
引号括起来的一系列字符组成。当然,如果你想显示一些特殊字符(比如换行符),你必须将转义版本放在那里(在这种情况下为\n
)。
还记得当我写read
期望值的字符串表示时?在您的情况下,read
期望完全此字符串格式。当然,它尝试做的第一件事就是匹配开头的报价。由于您的第一行 不以"
开头,read
会抱怨并崩溃该计划。
read "hello" :: String
失败的方式与read "1" :: [Int]
失败的方式相同;单独1
无法解析为Int
s的列表 - 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 ∘ show
一次。那么问题是什么?
show
以及show
[a]
某些内容列表的默认行为(例如a
Char
; [ firstElement , secondElement ... lastElement ]
除外获得特殊处理的{1}}是生成字符串[ ... ]
。如您所见,如果您想避开[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
和{{1}}。
答案 1 :(得分:2)
hGetContents handle
将句柄的内容读入字符串。要将所述字符串转换为行列表,您真的希望将其拆分为换行符。你不需要处理read
- 你只需要字符串。所以你只需要lines :: String -> [String]
。此外,您的代码似乎表明您正在“强制性地思考”,在阅读之前“初始化”list
。尝试一下
main = do
handle <- openFile "data.txt" ReadMode
contents <- hGetContents handle
let list = lines contents
print list
hClose handle
但是,有一些方法可以使您的程序更简单。如果您实际上不需要文件句柄来处理其他内容,只需调用readFile :: FilePath -> IO String
即可轻松获取文件中的所有文本。如果你不需要list
实际绑定到一个名字,你也可以摆脱它,将你的程序减少到
main = do
contents <- readFile "data.txt"
print (lines contents)
回顾一下这里发生的事情:读取“data.txt”,其整个内容可用contents :: String
。您希望将此字符串转换为字符串列表,每行一个字符串,即lines contents
确切的字符串。