【问题标题】:Haskell IO - file lines to listHaskell IO - 要列出的文件行
【发布时间】:2014-09-18 15:07:56
【问题描述】:

我正在编写一个 haskell 程序,但遇到了 IO 类型的问题。我的 readLines 函数和它应该返回的类型似乎有问题。我希望它返回一个列表,其中每个元素都是使用 openDict 函数打开的文件中的一行。

这是我的代码。

main :: IO ()
main = do
    fileHandle <- openDict
    readLines fileHandle
    putStr "End of program\n"

readLines :: Handle -> [IO String]
readLines fileHandle
    | isEOF <- hIsEOF fileHandle = []
    | otherwise                  = [hGetLine fileHandle] ++ readLines fileHandle

openDict :: IO Handle
openDict = do
    putStr "Enter file path: "
    filePath <- getLine
    openFile filePath ReadMode

这是错误:

Couldn't match type `[]' with `IO'
    Expected type: IO (IO String)
      Actual type: [IO String]
    In the return type of a call of `readLines'
    In a stmt of a 'do' block: readLines fileHandle
    In the expression:
      do { fileHandle <- openDict;
           readLines fileHandle;
           putStr "End of program" }

所以基本上,我怎样才能将这些来自 IO 的字符串存储在一个列表中?

【问题讨论】:

  • [IO String] 在这里没有多大意义。 (这并不是说它根本没有意义)。尝试使用IO [String]
  • 另外,| isEOF &lt;- hIsEOF fileHandle 并不代表您认为的意思。它实际上并不检查 EOF,它只是 binds hIsEOF 您应该稍后使用。这种语法对像 Maybe 这样的 monad 有意义,但对 IO 则不然。见paper

标签: list haskell io


【解决方案1】:

您的readLines 函数有几个问题。

首先,在这种情况下,您不想返回[IO String]。这将是一个 IO 操作列表,例如 [print 1 &gt;&gt; return "a", print 2 &gt;&gt; return "b"],这不是您想要的。您需要单个 IO 操作返回 Strings 列表,因此您需要使用 IO [String]

其次,模式守卫isEof &lt;- hIsEOF fileHandle 正在检查hIsEOF(这是一个IO 操作)返回的值是否为isEOF 形式,其中isEOF 是一个正在定义的变量。当然,anything 的格式为var,因为变量可以是任何东西。所以测试总是正确的,并且没有意义:例如,它甚至不运行 IO 操作。

第三,你组合列表构造函数和IO动作的方式遵循了当前的错误类型, 所以很混乱。

实现这一点的一个非常冗长的方法是:

readLines :: Handle -> IO [String]
readLines fileHandle = do
   eof <- hIsEOF fileHandle
   if eof then return []
          else do line <- hGetLine fileHandle
                  rest <- readLines fileHandle
                  return (line:rest)

使用Applicative 语法可以稍微缩短最后几行:

import Control.Applicative
readLines2 :: Handle -> IO [String]
readLines2 fileHandle = do
   eof <- hIsEOF fileHandle
   if eof then return []
          else (:) <$> hGetLine fileHandle <*> readLines2 fileHandle   

可以通过读取文件中的所有内容并在之后使用lines 库函数拆分为列表来获得简短版本。

import Control.Applicative
readLines3 :: Handle -> IO [String]
readLines3 fileHandle = lines <$> hGetContents fileHandle

【讨论】:

  • 感谢您解释我的代码中的错误以及修复它们的方法。我已决定将库函数作为选择的解决方案。
【解决方案2】:

这是您可能真正想要的readLines

readLines :: Handle -> IO [String]
readLines h = do
    isEOF <- hIsEOF h -- need to access isEOF directly, without monad, see comment about the bind
    case isEOF of
        True -> return [] -- the return adds the IO monad to the [String] type
        False -> do
            line <- hGetLine h -- need to access line separately without monad
            lines <- readLines h -- recurse down
            return $ line ++ lines -- return adds back the IO monad

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-09-22
    • 2019-03-28
    • 2021-03-24
    • 1970-01-01
    • 1970-01-01
    • 2016-03-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多