我认为您对 Haskell 中的 IO 存在根本性的误解。特别是,你这样说:
也许有一个函数可以将'IO String'转换为[Char]?
不,没有1,没有这样的功能是 Haskell 最重要的事情之一。
Haskell 是一种非常有原则的语言。它试图区分“纯”函数(没有任何副作用,并且在给出相同输入时总是返回相同的结果)和“不纯”函数(具有从文件读取、打印等副作用)到屏幕,写入磁盘等)。规则是:
- 您可以在任何地方使用纯函数(在其他纯函数中,或在不纯函数中)
- 您只能在其他不纯函数中使用不纯函数。
代码被标记为纯或不纯的方式是使用类型系统。当你看到像
这样的函数签名时
digitToInt :: String -> Int
你知道这个函数是纯粹的。如果你给它一个String,它会返回一个Int,而且如果你给它同样的String,它总是会返回同样的Int。另一方面,像
这样的函数签名
getLine :: IO String
是不纯,因为String的返回类型标有IO。显然getLine(读取一行用户输入)不会总是返回相同的String,因为它取决于用户输入的内容。你不能在纯代码中使用这个函数,因为即使添加最小的位杂质会污染纯代码。一旦你去了IO,你就再也回不去了。
您可以将IO 视为一个包装器。当您看到特定类型时,例如 x :: IO String,您应该将其解释为“x 是一个操作,当执行时,它会执行一些任意 I/O,然后返回 String 类型的内容”(注意在 Haskell 中,String 和 [Char] 完全一样)。
那么,您如何访问来自IO 操作的值?幸运的是,函数main 的类型是IO ()(这是一个执行一些I/O 并返回() 的操作,这与什么都不返回相同)。所以你总是可以在main 中使用你的IO 函数。当你执行一个 Haskell 程序时,你正在做的是运行 main 函数,这会导致程序定义中的所有 I/O 都被实际执行——例如,你可以从文件中读取和写入,询问用户输入,写入标准输出等。
你可以考虑这样构建一个 Haskell 程序:
- 所有执行 I/O 的代码都会获得
IO 标签(基本上,你将它放在 do 块中)
- 不需要执行 I/O 的代码不需要位于
do 块中 - 这些是“纯”函数。
- 您的
main 函数将您定义的 I/O 操作按顺序排列在一起,以使程序执行您希望它执行的操作(在您喜欢的任何地方穿插纯函数)。
- 当您运行
main 时,您会执行所有这些 I/O 操作。
那么,考虑到所有这些,您如何编写程序?嗯,函数
readFile :: FilePath -> IO String
将文件读取为String。所以我们可以使用它来获取文件的内容。功能
lines:: String -> [String]
在换行符上拆分String,所以现在你有一个Strings 列表,每个对应于文件的一行。功能
init :: [a] -> [a]
从列表中删除最后一个元素(这将删除每行最后的.)。功能
read :: (Read a) => String -> a
接受String 并将其转换为任意的Haskell 数据类型,例如Int 或Bool。合理地组合这些功能将为您提供您的程序。
请注意,您真正需要执行任何 I/O 的唯一时间是在读取文件时。因此,这是程序中唯一需要使用IO 标签的部分。程序的其余部分可以“纯粹”编写。
听起来你需要的是文章The IO Monad For People Who Simply Don't Care,它应该能解释你的很多问题。不要被“monad”这个词吓到——你不需要理解什么是 monad 来编写 Haskell 程序(请注意,这一段是我回答中唯一使用“monad”这个词的段落,尽管我承认我已经用了四次了……)
这是(我认为)你想要编写的程序
run :: IO (Int, Int, [(Int,Int,Int)])
run = do
contents <- readFile "text.txt" -- use '<-' here so that 'contents' is a String
let [a,b,c] = lines contents -- split on newlines
let firstLine = read (init a) -- 'init' drops the trailing period
let secondLine = read (init b)
let thirdLine = read (init c) -- this reads a list of Int-tuples
return (firstLine, secondLine, thirdLine)
要回答关于将lines 应用于readFile text.txt 的输出的npfedwards 评论,您需要意识到readFile text.txt 为您提供IO String,并且仅当您将其绑定到变量时(使用@ 987654369@),您可以访问底层String,以便您可以将lines应用于它。
记住:一旦你去了IO,你就再也回不去了。
1 我故意忽略unsafePerformIO,因为顾名思义,它非常不安全!除非您真的知道自己在做什么,否则永远不要使用它。