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 打印
打印::显示a => a -> IO ()
换句话说,print
接受一个参数,该参数非正式地知道如何显示自己(即,将其内容转换为字符串)并创建一个 I/O 操作,该操作在执行时输出该字符串。这或多或少是您期望print
的工作方式:
前奏>打印3
3
前奏>打印“嗨”
“你好”
我们知道我们想要从文件中print
获取Film
数据,但是,虽然很好,但 ghc 无法读懂我们的想法。但是在添加了类型提示之后
readFilms :: FilePath -> Film
readFilms = undefined
我们得到一个新的错误。
电影.hs:8:12:
无法匹配预期类型“IO t0”
具有实际类型 `(String, String, Int, [Rating])'
预期类型:IO t0
实际类型:电影
在“readFilms”调用的返回类型中
在“do”表达式中:电影 <- readFilms "ratings.dat"
该错误告诉您编译器对您的故事感到困惑。你说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, [])
这个版本可以编译,你甚至可以通过main
在 ghci 提示符下输入来运行它。
在dave4420 的回答中,有关于其他要使用的功能的很好的提示。把上面的方法想象成一个拼图游戏,其中各个部分都是函数。为了使您的程序正确,所有类型都必须组合在一起。您可以通过采取上述小步骤来朝着最终的工作程序取得进展,如果您的草图中有错误,类型检查器会通知您。
要弄清楚的事情:
- 如何将整个输入块转换为单独的行?
- 您如何确定您的程序正在检查的行是否是头衔、导演等?
- 您如何将文件 (a
String
) 中的年份转换为 anInt
以配合您的定义Film
?
- 你如何跳过空白或空行?
- 你如何使
readFilms
累积并返回Film
数据列表?