我是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
Run Code Online (Sandbox Code Playgroud)
以下是我的错误消息(对不起,这有点冗长)。我意识到我的某些逻辑可能有点缺陷,尤其是在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
| ^^^^^^
Run Code Online (Sandbox Code Playgroud)
是的,GHC错误消息可能令人莫名其妙,但是我将尝试与您讨论一下这组消息。第一条错误消息实际上是最难理解的消息之一,因此让我们跳到第二条。这个说:
- 当GHC正在查看的第一个参数时
(</>),即表达式doesDirectoryExist fry- 它预期找到一个
FilePath(因为(</>)运营商的第一个参数显然应该是一个FilePath)- 而是它ACTUALLY发现了一个
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
Run Code Online (Sandbox Code Playgroud)
如果您使用此版本重新编译,则第一条错误消息已经有所更改,但是仍然令人困惑。但是,第二条错误消息已消失,因此情况正在改善!!第三条错误消息(现在实际上是第二条错误消息)与以前相同。它说:
- 当GHC在查看表达式时
fry(的第一个参数doesDirectoryExist)- 它期望一个
FilePath- 但实际上找到了
[FilePath]
这很奇怪!我们也期望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
Run Code Online (Sandbox Code Playgroud)
如果我们重新编译,我们将减少三个错误。与以往一样顽固的第一个错误仍然令人困惑。第二条错误消息是原始列表中第五条消息的变体:
Run Code Online (Sandbox Code Playgroud)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
Run Code Online (Sandbox Code Playgroud)
但是,这并不能真正清除错误。现在,GHC告诉我们:
Run Code Online (Sandbox Code Playgroud)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和else分支if必须具有相同的类型,但是您试图在一个分支上printDirectory返回文件列表(因为返回type IO [FilePath]),但是在另一个分支上打印文件名(具有type IO ())。 。
我想您必须在这里决定是要打印文件还是返回文件。您在问题中说您想打印它们,所以我猜您的printDirectory签名是错误的。如果您只是打印,那么这是一个IO操作,不会返回任何内容(或至少没有任何有用的内容),因此签名应显示为:
printDirectory :: FilePath -> IO ()
Run Code Online (Sandbox Code Playgroud)
如果重新编译,将出现两个错误。第一个与以前相同,第二个与原始列表中的最后一个错误相同:
Run Code Online (Sandbox Code Playgroud)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)。无论如何,如果我们将其删除,则只剩下一个错误:
Run Code Online (Sandbox Code Playgroud)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块的类型就是其最后一条语句的类型,GHC决定以某种方式确定整个外部do块应具有该类型FilePath -> IO (),这就是为什么它希望最后一条语句具有该类型。为什么它认为外部do-block应该具有type FilePath -> 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"
Run Code Online (Sandbox Code Playgroud)
现在错误消息显示为:
Run Code Online (Sandbox Code Playgroud)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 xxx与IO (IO xxx)不匹配在代码中突然出现,它通常是因为你写了一个做块语句为:
let x = something
Run Code Online (Sandbox Code Playgroud)
应该是什么时候:
x <- something
Run Code Online (Sandbox Code Playgroud)
在这里,如果我们检查getCurrentDirectory >>= getDirectoryContentsGHCi中的类型,我们会看到它具有以下类型:
> :t getCurrentDirectory >>= getDirectoryContents
getCurrentDirectory >>= getDirectoryContents :: IO [FilePath]
Run Code Online (Sandbox Code Playgroud)
因此,返回文件路径列表是一项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"
Run Code Online (Sandbox Code Playgroud)
现在,forM语句上的类型仍然不匹配,但是我们越来越接近:
Run Code Online (Sandbox Code Playgroud)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“单元” AKA的动作()),而是forM试图返回整个列表中()。当您使用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"
Run Code Online (Sandbox Code Playgroud)
它输入检查没有错误!
不幸的是,如果您尝试运行它,它将陷入无休止的循环,但这是因为递归被破坏了。目录内容包括特殊条目,您将要跳过这些条目".",".."但是即使您解决了该问题,该函数实际上也不会更改当前目录,因此,如果至少有一个子目录,它将继续检查当前目录一遍又一遍。
所以,我想它仍然是调试时间!