【问题标题】:Reading file into types - Haskell将文件读入类型 - Haskell
【发布时间】:2012-04-16 13:40:26
【问题描述】:

现在我有两种类型:

type Rating = (String, Int)

type Film = (String, String, Int, [Rating])

我有一个包含这些数据的文件:

"Blade Runner"
"Ridley Scott"
1982
("Amy",5), ("Bill",8), ("Ian",7), ("Kevin",9), ("Emma",4), ("Sam",7), ("Megan",4)

"The Fly"
"David Cronenberg"
1986
("Megan",4), ("Fred",7), ("Chris",5), ("Ian",0), ("Amy",6)

如何查看将所有条目存储到类似 FilmDatabase = [Film] 的文件?

【问题讨论】:

  • 只是简单的事情,例如:displayFile :: IO () displayFile = do putStrLn "输入文件名:" name
  • 那么您遇到了哪些问题?
  • 我不明白如何过滤行并将它们添加到正确的类型,例如每个条目的第一行应该是一个字符串并且应该添加到电影的标题(标题等...这类东西在 C# 中很简单,但在 Haskell 中我发现它非常困难。
  • 我在回答中添加了一些提示。

标签: file haskell input


【解决方案1】:

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 数据列表?

【讨论】:

    【解决方案2】:

    这是作业吗?

    您可能会发现这些功能很有用:

    请记住String[Char] 相同。

    一些线索:

    • dropWhile null 将删除列表开头的空行
    • break null 将列表拆分为非空行的前导运行,以及列表的其余部分

    【讨论】:

      【解决方案3】:

      Haskell 有一个很好的方法来使用类型来找到正确的函数。例如:在 Gregs 的回答中,他希望您弄清楚(除其他外)如何将电影的年份从 String 转换为 Int。好吧,你需要一个函数。该函数的类型应该是什么?它接受一个字符串并返回一个 Int,所以类型应该是String -&gt; Int。完成后,转到Hoogle 并输入该类型。这将为您提供具有相似类型的函数列表。您需要的函数实际上有一个稍微不同的类型 - Read a =&gt; String -&gt; a - 所以它有点落后,但猜测类型然后扫描结果列表通常是一种非常有用的策略。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-05-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-04-02
        相关资源
        最近更新 更多