首页 > 解决方案 > 运行程序时出错

问题描述

我是 Haskell 的新手,我正在尝试创建一个程序来搜索目录并打印目录及其子目录中的文件列表。我一直在调试我的错误。我不知道出了什么问题,不幸的是,我在网上找不到有用的文档和各种教程辅助工具。

这是我想出的代码。但是,我不知道它是否有效,因为我无法通过调试错误。

import Control.Monad
import System.Directory
import System.FilePath
import System.Posix.Files

printDirectory :: FilePath -> IO [FilePath]
printDirectory = do
    let filesInCurDir = getCurrentDirectory >>= getDirectoryContents
    forM filesInCurDir $ \fry -> do
      isDirectory <- doesDirectoryExist fry </>
        if isDirectory
           then do printDirectory
           else putStrLn fry
      putStrLn "Directory search completed"
    return

以下是我的错误消息(对不起,有点冗长)。我意识到我的一些逻辑可能有点缺陷,尤其是 if 语句中的递归调用。不幸的是,我无法通过调试甚至开始修复逻辑。请有人可以帮助解释为什么我会收到我遇到的错误以及如何解决这些错误。

--错误信息--

ass3.hs:13:9: error:
    • Couldn't match expected type ‘FilePath -> IO [FilePath]’
                  with actual type ‘[b0]’
    • In a stmt of a 'do' block:
        forM filesInCurDir
          $ \ fry
              -> do isDirectory <- doesDirectoryExist fry
                                     </> if isDirectory then ... else putStrLn fry
                    putStrLn "Directory search completed"
      In the expression:
        do let filesInCurDir = getCurrentDirectory >>= getDirectoryContents
           forM filesInCurDir $ \ fry -> do ...
           return
      In an equation for ‘printDirectory’:
          printDirectory
            = do let filesInCurDir = ...
                 forM filesInCurDir $ \ fry -> ...
                 return
   |
13 |         forM filesInCurDir $ \fry -> do
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^...

ass3.hs:14:31: error:
    • Couldn't match type ‘IO Bool’ with ‘[Char]’
      Expected type: FilePath
        Actual type: IO Bool
    • In the first argument of ‘(</>)’, namely ‘doesDirectoryExist fry’
      In a stmt of a 'do' block:
        isDirectory <- doesDirectoryExist fry
                         </> if isDirectory then do printDirectory else putStrLn fry
      In the expression:
        do isDirectory <- doesDirectoryExist fry
                            </> if isDirectory then do printDirectory else putStrLn fry
           putStrLn "Directory search completed"
   |
14 |                 isDirectory <-doesDirectoryExist fry</>
   |                               ^^^^^^^^^^^^^^^^^^^^^^

ass3.hs:14:50: error:
    • Couldn't match type ‘[Char]’ with ‘Char’
      Expected type: FilePath
        Actual type: [FilePath]
    • In the first argument of ‘doesDirectoryExist’, namely ‘fry’
      In the first argument of ‘(</>)’, namely ‘doesDirectoryExist fry’
      In a stmt of a 'do' block:
        isDirectory <- doesDirectoryExist fry
                         </> if isDirectory then do printDirectory else putStrLn fry
   |
14 |                 isDirectory <-doesDirectoryExist fry</>
   |                                                  ^^^

ass3.hs:15:28: error:
    • Couldn't match expected type ‘Bool’
                  with actual type ‘FileStatus -> Bool’
    • Probable cause: ‘isDirectory’ is applied to too few arguments
      In the expression: isDirectory
      In the second argument of ‘(</>)’, namely
        ‘if isDirectory then do printDirectory else putStrLn fry’
      In a stmt of a 'do' block:
        isDirectory <- doesDirectoryExist fry
                         </> if isDirectory then do printDirectory else putStrLn fry
   |
15 |                         if isDirectory
   |                            ^^^^^^^^^^^

ass3.hs:16:41: error:
    • Couldn't match type ‘FilePath -> IO [FilePath]’ with ‘[Char]’
      Expected type: FilePath
        Actual type: FilePath -> IO [FilePath]
    • Probable cause: ‘printDirectory’ is applied to too few arguments
      In a stmt of a 'do' block: printDirectory
      In the expression: do printDirectory
      In the second argument of ‘(</>)’, namely
        ‘if isDirectory then do printDirectory else putStrLn fry’
   |
16 |                                 then do printDirectory
   |                                         ^^^^^^^^^^^^^^

ass3.hs:17:30: error:
    • Couldn't match type ‘IO ()’ with ‘[Char]’
      Expected type: FilePath
        Actual type: IO ()
    • In the expression: putStrLn fry
      In the second argument of ‘(</>)’, namely
        ‘if isDirectory then do printDirectory else putStrLn fry’
      In a stmt of a 'do' block:
        isDirectory <- doesDirectoryExist fry
                         </> if isDirectory then do printDirectory else putStrLn fry
   |
17 |                         else putStrLn fry
   |                              ^^^^^^^^^^^^

ass3.hs:17:39: error:
    • Couldn't match type ‘[Char]’ with ‘Char’
      Expected type: String
        Actual type: [FilePath]
    • In the first argument of ‘putStrLn’, namely ‘fry’
      In the expression: putStrLn fry
      In the second argument of ‘(</>)’, namely
        ‘if isDirectory then do printDirectory else putStrLn fry’
   |
17 |                         else putStrLn fry
   |                                       ^^^

ass3.hs:18:17: error:
    • Couldn't match type ‘IO’ with ‘[]’
      Expected type: [()]
        Actual type: IO ()
    • In a stmt of a 'do' block: putStrLn "Directory search completed"
      In the expression:
        do isDirectory <- doesDirectoryExist fry
                            </> if isDirectory then do printDirectory else putStrLn fry
           putStrLn "Directory search completed"
      In the second argument of ‘($)’, namely
        ‘\ fry
           -> do isDirectory <- doesDirectoryExist fry
                                  </> if isDirectory then ... else putStrLn fry
                 putStrLn "Directory search completed"’
   |
18 |                 putStrLn "Directory search completed"
   |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

ass3.hs:19:9: error:
    • Couldn't match expected type ‘[b0]’
                  with actual type ‘a0 -> m0 a0’
    • Probable cause: ‘return’ is applied to too few arguments
      In a stmt of a 'do' block: return
      In the expression:
        do let filesInCurDir = getCurrentDirectory >>= getDirectoryContents
           forM filesInCurDir $ \ fry -> do ...
           return
      In an equation for ‘printDirectory’:
          printDirectory
            = do let filesInCurDir = ...
                 forM filesInCurDir $ \ fry -> ...
                 return
   |
19 |         return  
   |         ^^^^^^

标签: haskellsyntax

解决方案


是的,GHC 错误消息可能非常令人费解,但我会尝试通过这组内容与您交谈。第一个错误消息实际上是最难理解的,所以让我们跳到第二个。这个说:

  • 当 GHC 查看 的第一个参数时(</>),即表达式doesDirectoryExist fry
  • 预计找到 a FilePath(因为(</>)运算符的第一个参数显然应该是 a FilePath
  • 但实际上它发现了一个IO Bool

如果您检查 的类型doesDirectoryExist,您可以看到——确实——它需要 aFilePath并返回一个IO Bool,所以 GHC 是正确的,你不能提供 a doesDirectoryExist fry(它具有类型IO Bool)作为某种FilePath.

我不太确定您尝试与哪些路径组合(</>),但如果我们完全摆脱该运算符并重新格式化,以下看起来更像您的意图:

printDirectory :: FilePath -> IO [FilePath]
printDirectory = do
    let filesInCurDir = getCurrentDirectory >>= getDirectoryContents
    forM filesInCurDir $ \fry -> do
      isDirectory <- doesDirectoryExist fry
      if isDirectory
         then do printDirectory
         else putStrLn fry
      putStrLn "Directory search completed"
    return

如果你用这个版本重新编译,第一条错误信息会发生一些变化,但仍然令人困惑。但是,第二条错误消息已经消失,所以情况正在改善!!第三条错误消息(现在实际上是第二条错误消息)与以前相同。它说:

  • 当 GHC 正在查看表达式时fry( 的第一个参数doesDirectoryExist
  • 期望一个FilePath
  • 但它实际上发现了一个[FilePath]

这很奇怪!我们也期望一个s FilePath,而不是一个完整的FilePaths 列表。那forM是应该做的。这里发生的情况是其他一些不明显的错误导致 GHC 错误输入fry[FilePath]而不是FilePath. 为了解决这个问题,让我们通过使用语句覆盖frys 值来伪造它:let

printDirectory :: FilePath -> IO [FilePath]
printDirectory = do
    let filesInCurDir = getCurrentDirectory >>= getDirectoryContents
    forM filesInCurDir $ \fry -> do
      let fry = "__FAKEFILEPATH__" -- DEBUGGING       -- << CHANGE HERE
      isDirectory <- doesDirectoryExist fry
      if isDirectory
         then do printDirectory
         else putStrLn fry
      putStrLn "Directory search completed"
    return

如果我们重新编译,就会出现三个错误。第一个错误,一如既往的顽固,仍然令人困惑。第二条错误消息是原始列表中第五条消息的变体:

Directory.hs:13:18: error:
      • Couldn't match expected type ‘IO ()’
                    with actual type ‘FilePath -> IO [FilePath]’
      • Probable cause: ‘printDirectory’ is applied to too few arguments
        In a stmt of a 'do' block: printDirectory
        In the expression: do printDirectory

在这里,GHC 认为表达式do printDirectory应该有 type ,IO ()但有 type FilePath -> IO [FilePath],这有助于表明您调用printDirectory的参数太少(这是真的,因为printDirectory需要文件路径)。让我们暂时提供fry,即使我们稍后可能需要做一些不同的事情来获得正确的递归。

printDirectory :: FilePath -> IO [FilePath]
printDirectory = do
    let filesInCurDir = getCurrentDirectory >>= getDirectoryContents
    forM filesInCurDir $ \fry -> do
      let fry = "__FAKEFILEPATH__" -- DEBUGGING
      isDirectory <- doesDirectoryExist fry
      if isDirectory
         then do printDirectory fry -- FIXME      -- << CHANGE HERE
         else putStrLn fry
      putStrLn "Directory search completed"
    return

但是,这并不能真正清除错误。现在,GHC 告诉我们:

Directory.hs:14:15: error:
      • Couldn't match type ‘()’ with ‘[FilePath]’
        Expected type: IO [FilePath]
          Actual type: IO ()
      • In the expression: putStrLn fry
        In a stmt of a 'do' block:
          if isDirectory then do printDirectory fry else putStrLn fry

基本上,在 Haskell 中,语句的thenandelse分支if必须具有相同的类型,但是您试图在一个分支上返回文件列表(因为printDirectory返回 type IO [FilePath]),但在另一个分支上打印文件名(具有 type IO ()) .

我想您必须在这里决定是要打印文件还是返回文件。你在你的问题中说你想打印它们,所以我猜你的printDirectory签名是错误的。如果您只是打印,那么这是一个不返回任何内容(或至少没有任何有用的内容)的 IO 操作,因此签名应为:

printDirectory :: FilePath -> IO ()

如果你重新编译,你会得到两个错误。第一个与以前相同,第二个与原始列表中的最后一个错误相同:

Directory.hs:15:5: error:
      • Couldn't match expected type ‘IO b0’
                    with actual type ‘a0 -> m0 a0’
      • Probable cause: ‘return’ is applied to too few arguments

你程序的最后一行似乎有一个奇怪的实际类型。值得庆幸的是,GHC 解释说您可能忘记向return. 事实上,不清楚你想在这里返回什么(当你发布你的代码时,你似乎已经把它遗漏了,所以也许你已经决定删除​​这个return)。无论如何,如果我们放弃它,我们只剩下一个错误:

Directory.hs:9:5: error:
      • Couldn't match expected type ‘FilePath -> IO ()’
                    with actual type ‘IO (IO ())’
      • In a stmt of a 'do' block:
          forM filesInCurDir ...

在这里,GHC 决定forM ...你的 do-block 中的语句应该有 type FilePath -> IO (),但它实际上有 type IO (IO b)

这令人困惑,但实际类型和预期类型都是错误的!该forM语句应该是打印一堆文件路径的 IO 操作,所以它应该有 type IO ()

这就是发生的事情。在 Haskell 中,do-block 的类型是其最后一条语句的类型,而 GHC 以某种方式决定整个外部 do-block 应该具有类型FilePath -> IO (),这就是它希望最后一条语句具有该类型的原因。为什么它认为外部 do-block 应该有 typeFilePath -> IO ()而不是IO ()?好吧,因为你告诉它printDirectory应该有类型FilePath -> IO (),然后直接将 do-block 绑定到printDirectory而不给出printDirectory参数。你需要这样写printDirectory dir = do ...

printDirectory :: FilePath -> IO ()
printDirectory dir = do                        -- << CHANGE HERE
    let filesInCurDir = getCurrentDirectory >>= getDirectoryContents
    forM filesInCurDir $ \fry -> do
      let fry = "__FAKEFILEPATH__" -- DEBUGGING
      isDirectory <- doesDirectoryExist fry
      if isDirectory
         then do printDirectory fry -- FIXME
         else putStrLn fry
      putStrLn "Directory search completed"

现在错误消息显示:

Directory.hs:9:5: error:
      • Couldn't match type ‘IO ()’ with ‘()’
        Expected type: IO ()
          Actual type: IO (IO ())
      • In a stmt of a 'do' block:
          forM filesInCurDir ...

当您看到代码中出现IO xxxvsIO (IO xxx)不匹配时,通常是因为您将 do-block 语句编写为:

let x = something

什么时候应该是:

x <- something

在这里,如果我们检查getCurrentDirectory >>= getDirectoryContentsGHCi 中的类型,我们会看到它具有以下类型:

> :t getCurrentDirectory >>= getDirectoryContents
getCurrentDirectory >>= getDirectoryContents :: IO [FilePath]

所以返回文件路径列表是一个 IO 操作。但是,我们已将其分配给letto filesInCurDir。但我们不想filesInCurDir成为一个 IO动作,我们希望它是实际的文件列表。为此,我们需要使用<-代替let

printDirectory :: FilePath -> IO ()
printDirectory dir = do
    filesInCurDir <- getCurrentDirectory >>= getDirectoryContents    -- << CHANGE HERE
    forM filesInCurDir $ \fry -> do
      let fry = "__FAKEFILEPATH__" -- DEBUGGING
      isDirectory <- doesDirectoryExist fry
      if isDirectory
         then do printDirectory fry -- FIXME
         else putStrLn fry
      putStrLn "Directory search completed"

现在,我们的语句仍然存在类型不匹配forM,但我们越来越接近:

Directory.hs:9:5: error:
      • Couldn't match type ‘[()]’ with ‘()’
        Expected type: IO ()
          Actual type: IO [()]
      • In a stmt of a 'do' block:
          forM filesInCurDir

GHC 期望forM具有类型IO ()(即,执行一些打印并且不返回任何内容 AKA “unit” AKA 的操作()而是forM尝试返回. 当您使用(它构建一个要返回的列表)代替(它只是针对其副作用运行一些 IO 操作,例如打印,但本身不返回任何东西)时,就会发生这种情况。因此,您需要替换为,您现在可以安全地删除“DEBUGGING”语句:()forMforM_forMforM_

printDirectory :: FilePath -> IO ()
printDirectory dir = do
    filesInCurDir <- getCurrentDirectory >>= getDirectoryContents
    forM_ filesInCurDir $ \fry -> do      -- << CHANGE HERE
                                          -- << REMOVE DEBUGGING
      isDirectory <- doesDirectoryExist fry
      if isDirectory
         then do printDirectory fry -- FIXME
         else putStrLn fry
      putStrLn "Directory search completed"

它类型检查没有错误!

不幸的是,如果您尝试运行它,它只会进入无限循环,但那是因为递归被破坏了。目录内容包括您想要跳过的特殊条目"."".."但即使您修复它,该函数实际上也不会更改当前目录,因此如果至少有一个子目录,它只会继续检查当前目录一遍又一遍。

所以,我想它仍然是调试时间!


推荐阅读