3

我正在尝试在 Haskell 中进行一些编程。我正在尝试读取文件,然后使用 line 函数将文件中的每一行放在一个列表中。这是部分代码:

file = "muh.rtr"
readTrack :: String -> Track
readTrack file =
    do      let defFile = readFile file
            let fileLines = lines defFile

但是,我不断收到此错误:

Parser.hs:22:39:
    Couldn't match expected type `String' with actual type `IO String'
    In the first argument of `lines', namely `defFile'
    In the expression: lines defFile
    In an equation for `fileLines': fileLines = lines defFile

我已经在互联网上搜索了几个小时,希望能在某个地方找到一些答案,但到目前为止我还没有这么幸运。

4

5 回答 5

9

你可能想要这样的东西:

readTrack :: String -> IO Track
readTrack file = do defFile <- readFile file
                    let fileLines =  lines defFile
                    -- etc....

...或类似的东西:

readTrack :: String -> IO Track
readTrack file = do fileLines <- liftM lines (readFile file)
                    -- etc....

但是你真正应该做的是停下来,去找一个语言介绍,比如Learn You a Haskell,然后花点时间阅读它。

将完全由非常简单的错误组成的代码输入 GHC,然后在 Stack Overflow 上发布错误消息并不是一个好的学习方法。

于 2012-09-25T20:18:07.833 回答
6

的类型readFile

readFile :: FilePath -> IO String 

所以你需要使用<-来绑定结果,并且你的函数必须返回IO Track

readTrack :: String -> IO Track
readTrack file =
  do defFile <- readFile file
     let fileLines = lines defFile
     ...

我建议阅读有关 Haskell IO 的优秀教程,例如Learn You a Haskell for Great Good 的输入和输出章节!.

于 2012-09-25T20:17:55.587 回答
5

readFile 返回一个IO string. 也就是说,它是一个返回字符串的 IO 计算。这意味着您需要使用<-而不是let“获取”它返回的字符串。

 readTrack file =
    do
        defFile <- readFile file
        ...

您可以使用let绑定不是 IO 计算的东西,例如行的返回值,即常规字符串。

readTrack file =
    do
        defFile <- readFile file
        let fileLines = lines defFile
        ...

最后,您需要返回您可能想要尝试的值

readTrack file =
    do
        defFile <- readFile file
        let fileLines = lines defFile
        fileLines --Doesn't actually work!

但不幸的是,由于我们在一个“do”块内并试图返回一个单子计算,我们需要将 fileLines 发送回 io monad(请记住,out 函数返回IO [String],而不是String

readTrack file =
    do
        defFile <- readFile file
        let fileLines = lines defFile
        return fileLines

请注意,这里的“return”不是在大多数语言中通常可以找到的 return 语句,并且不应在纯函数中使用它。

乍一看,这一切似乎很多。我建议您坚持使用纯函数(没有输入和输出/单子),直到您更好地掌握该语言。

于 2012-09-25T20:18:23.760 回答
4

You can't do it like that -- you've run into the IO monad. What you need to do is something like:

readTrack :: String -> IO Track
readTrack file = do
   defFile <- readFile file
   let fileLines = lines deffile
   ...
   return whatever

Think of IO T values as statements (as opposed to expressions) with return type T. Because statements have side effects, but expressions don't, you can never turn a statement into an expression; the type system enforces this, which is why your type signature won't work.

Note the different assignment-like syntax in the do block: in this example, the foo <- bar is used for IO operations, while the let baz = quux syntax is used for purely functional evaluation. This is more fallout from using monadic I/O -- it makes more sense in the full generality of Haskell's polymorphic type system, but it's not necessarily bad to have a syntactic indicator of pure vs. side-effecting operations, either.

In general, it is good practice to try keeping most of your implementation in the purely functional realm: implement your pure computation with regular functional methods, then describe your I/O operations in the IO monad. It is a common novice mistake to write loops in the IO monad which would be more appropriate as list comprehensions or recursive functions.

于 2012-09-25T20:24:03.823 回答
3

如果你的函数应该有 type readTrack :: String -> Track,你确定 String 是一个文件名吗?也许是数据 - 如果是这样,请不要使用readFile. 编写一些示例数据并使用它进行测试,例如

sampleData = "2 3\n1 30 234 45\n1 2 32 4\n5 3 4 23"

(关于此作业的 SO 上的另一个问题没有使用文件 IO。我不会链接到它,因为您正处于危机之中并且可能想要复制,并且无论如何如果您拒绝学习 haskell,至少我'会迫使你提高你的 StackOverflow 搜索技能!:) )

无论如何,我认为通过解决字符串问题比解决 IO 问题你会得到更多的分数。

延迟 readFile 问题,直到你得到纯版本工作,否则你可能最终会在 IO monad 中编写大部分代码,这将比必要的复杂得多。

一个你有一个纯函数readTrack :: String -> Track,你可以做

readTrackFrom :: FilePath -> IO Track
readTrackFrom filename = fmap readTrack (readFile filename)

现在,fmap :: Functor f => (a -> b) -> f a -> f b, so 采用纯函数并将它们提升到在不同的计算上下文中工作,例如 IO。

因为IO是 a Functor(明天查,不是今晚查),所以我们使用它作为 type (String -> Track) -> IO String -> IO Track。这很好,因为readTrack :: String -> Track(readFile filename) :: IO String

如果你愿意,你可以然后>>= print或者>>= writeFile newfilename你认为合适。

deriving Show使用后不要忘记添加data Track =...,但如果你正在使用则不需要type Track = ....

于 2012-09-25T21:41:56.790 回答