运行程序时发生错误

时间:2019-07-01 20:56:20

标签: haskell syntax

我是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  
   |         ^^^^^^

1 个答案:

答案 0 :(得分:6)

是的,GHC错误消息可能令人莫名其妙,但是我将尝试与您讨论该设置。第一条错误消息实际上是最难理解的消息之一,因此让我们跳到第二条。这个人说:

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

如果您检查doesDirectoryExist的类型,则可以看到–实际上–它需要FilePath并返回IO Bool,所以GHC是正确的,您不能提供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正在查看表达式frydoesDirectoryExist的第一个参数)时
  •   
  • 期望一个FilePath
  •   
  • 实际上找到了一个[FilePath]
  •   

这很奇怪!我们也期望有FilePath,而不是FilePath的完整列表。这就是forM应该做的。这里发生的是,其他一些不明显的错误导致GHC将fry误输入为[FilePath]而不是FilePath。要解决此问题,让我们用fry语句覆盖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的类型应该为IO (),而类型为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中,then语句的elseif分支必须具有相同的类型,但是您试图返回一个分支上的文件列表(因为printDirectory返回类型IO [FilePath]),但在另一个分支上 print 打印文件名(类型为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决定您的do块中的forM ...语句应该具有类型FilePath -> IO (),但实际上具有类型IO (IO b)

这令人困惑,但是实际类型和预期类型都不正确! forM语句应该是一个IO操作,用于打印一堆文件路径,因此它应该具有类型IO ()

这是发生了什么。在Haskell中,do块的类型就是其最后一条语句的类型,GHC决定以某种方式确定整个外部do块应具有类型FilePath -> IO (),这就是为什么它希望最后一条语句具有该类型的原因。 。为什么认为外部do-block应该具有类型FilePath -> IO ()而不是IO ()?好吧,因为您告诉过printDirectory应该具有类型FilePath -> IO (),然后将do块直接绑定到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 xxxIO (IO xxx)不匹配的情况时,通常是因为您编写的do-block语句为:

let x = something

应该在什么时候出现:

x <- something

在这里,如果我们检查GHCi中的getCurrentDirectory >>= getDirectoryContents的类型,我们会看到它的类型:

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

所以返回文件路径列表是IO操作。但是,我们已将let分配给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 ()),而相反,forM是尝试返回()的整个 list 。当您使用forM(将建立一个要返回的列表)代替forM_(会为它们的副作用(例如打印而运行一些IO操作,但自身不返回任何内容))运行时,会发生这种情况。因此,您需要将forM替换为forM_,现在可以安全地删除“ DEBUGGING”语句:

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"

它输入检查没有错误!

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

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