Haskell 提供了一种独特的方式来勾勒您的方法。从你知道的开始
module Main where
type Rating = (String, Int)
type Film = (String, String, Int, [Rating])
main :: IO ()
main = do
films <- readFilms "ratings.dat"
print films
尝试将此程序加载到 ghci 中会产生
films.hs:8:12:不在范围内:`readFilms'
它需要知道readFilms 是什么,所以添加足够的代码以继续移动。
readFilms = undefined
这个函数应该做一些与Film 数据相关的事情。重新加载此代码(使用:reload 命令或简称:r)获取
电影.hs:9:3:
约束中的模糊类型变量“a0”:
(Show a0) 使用 `print' 引起的
...
print的类型是
前奏曲> :t 打印
print :: Show a => a -> IO()
换句话说,print 采用单个参数,非正式地知道如何显示自己(即,将其内容转换为字符串)并创建一个 I/O 操作,该操作在执行时输出该字符串。这或多或少是您期望print 工作的方式:
Prelude> 打印 3
3
前奏>打印“嗨”
“嗨”
我们知道我们想要 print 文件中的 Film 数据,但是,虽然很好,但 ghc 无法读懂我们的想法。但是在添加了类型提示之后
readFilms :: FilePath -> Film
readFilms = undefined
我们得到一个新的错误。
电影.hs:8:12:
无法匹配预期类型“IO t0”
具有实际类型 `(String, String, Int, [Rating])'
预期类型:IO t0
实际类型:电影
在“readFilms”调用的返回类型中
在“do”表达式中:电影
错误告诉您编译器对您的故事感到困惑。你说readFilms 应该给它一个Film,但是你在main 中调用它的方式,计算机应该首先执行一些I/O,然后然后返回Film数据。
在 Haskell 中,这是 纯 字符串(例如 "JamieB")和副作用(例如在提示您输入 Stack Overflow 用户名后从键盘读取您的输入)之间的区别。
所以现在我们知道我们可以将readFilms 绘制为
readFilms :: FilePath -> IO Film
readFilms = undefined
然后代码编译! (但我们还不能运行它。)
要深入挖掘另一层,假设单个电影的名称是 ratings.dat 中的唯一数据,并在其他任何地方放置占位符以使类型检查器满意。
readFilms :: FilePath -> IO Film
readFilms path = do
alldata <- readFile path
return (alldata, "", 0, [])
这个版本可以编译,你甚至可以通过在 ghci 提示符下输入 main 来运行它。
dave4420’s answer 中提供了有关其他要使用的功能的重要提示。把上面的方法想象成一个拼图游戏,其中各个部分都是函数。为了使您的程序正确,所有类型都必须组合在一起。如上所述,您可以通过采取一些小步骤来朝着最终的工作程序迈进,如果您的草图中有错误,类型检查器会通知您。
要弄清楚的事情:
- 如何将整个输入块转换为单独的行?
- 如何确定程序正在检查的行是标题、导演等?
- 如何将文件中的年份(
String)转换为Int,以配合您对Film 的定义?
- 如何跳过空白行或空行?
- 如何让
readFilms 累积并返回Film 数据列表?