我是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
| ^^^^^^
答案 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正在查看表达式
fry
(doesDirectoryExist
的第一个参数)时- 它期望一个
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
语句的else
和if
分支必须具有相同的类型,但是您试图返回一个分支上的文件列表(因为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 xxx
与IO (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"
它输入检查没有错误!
不幸的是,如果您尝试运行它,它将陷入无休止的循环,但这是因为递归被破坏了。目录内容包括特殊条目"."
和".."
,您将要跳过这些特殊条目,但是即使您进行了修复,该函数实际上也不会更改当前目录,因此,如果至少有一个子目录,它会不断检查当前目录。
所以,我想仍然是调试时间!