Reputation: 185
I am new to Haskell and I am trying to create a program that searches a directory and prints a list of files in the directory and its sub directories. I am stuck with debugging my errors. I have no clue what is wrong and unfortunately, I am not finding the documentation and various tutorial aids online to be helpful.
This is the code that I have come up with. However, I have no idea if it works because I cannot get past debugging the errors.
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
Below are my error messages (sorry, it is a bit lengthy). I realize that some of my logic may be a little flawed, particularly with the recursive call in the if statement. Unfortunately, I can't get past the debugging to even begin to fix the logic. Please can someone help explain why I am getting the errors I am getting and how to fix them.
--Error Messages--
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
| ^^^^^^
Upvotes: 0
Views: 141
Reputation: 51159
Yes, GHC error message can be pretty baffling, but I'll try to talk you through this set. The first error message is actually one of the hardest to understand, so let's skip to the second. This one says that:
- when GHC was looking at the first argument to
(</>)
, namely the expressiondoesDirectoryExist fry
- it EXPECTED to find a
FilePath
(because the(</>)
operator's first argument is obviously supposed to be aFilePath
)- but instead it ACTUALLY found an
IO Bool
If you check the type of doesDirectoryExist
, you can see that -- indeed -- it takes a FilePath
and returns an IO Bool
, so GHC is right, you cannot supply a doesDirectoryExist fry
(which has type IO Bool
) as some sort of FilePath
.
I'm not quite sure what paths you were trying to combine with (</>)
, but if we get rid of that operator entirely and reformat, the following looks more like what you intended:
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 you recompile with this version, the first error message has changed a bit, but it's still confusing. However, the second error message has disappeared, so things are improving!! The third error message (now actually the second error message) is the same as before. It says that:
- when GHC was looking at the expression
fry
(the first argument todoesDirectoryExist
)- it expected a
FilePath
- but it actually found a
[FilePath]
This is strange! We expected a FilePath
, too, not a whole list of FilePath
s. That's what the forM
was supposed to do. What's happened here is that some other error, which isn't obvious, has caused GHC to mis-type fry
as [FilePath]
instead of FilePath
. To work around this, let's fake it by overriding fry
s value with a let
statement:
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
If we recompile, we're down to three errors. The first error, stubborn as ever, is still confusing. The second error message is a variant of the fifth message in the original list:
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
Here, GHC feels that the expression do printDirectory
should have had type IO ()
but instead had type FilePath -> IO [FilePath]
, and it helpfully suggests that you've called printDirectory
with too few arguments (which is true, as printDirectory
needs a file path). Let's supply fry
for now, even though we might need to do something different later to get the recursion right.
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
This doesn't really clear up the error, though. Now, GHC tells us that:
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
Basically, in Haskell, the then
and else
branches of an if
statement have to have the same type, but you're trying to return a list of files on one branch (because printDirectory
returns type IO [FilePath]
) but print a filename (which has type IO ()
) on the other branch.
I guess you have to decide here if you want to print files or return files. You said in your question you wanted to print them, so I'm going to guess that your printDirectory
signature is wrong. If you're just printing, then that's an IO action that doesn't return anything (or at least not anything useful), so the signature ought to read:
printDirectory :: FilePath -> IO ()
If you recompile, you'll get two errors. The first is the same as before, the second is the same as the last error on your original list:
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
The last line of your program seems to have a weird actual type. Thankfully, GHC explains that you probably forget to supply an argument to return
. In fact, it's not clear what you were trying to return here (and when you posted your code, you seemed to have left it out, so maybe you already decided to delete this return
). Anyway, if we drop it, we're left with only one error:
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 ...
Here, GHC has decided that the forM ...
statement in your do-block should have had type FilePath -> IO ()
, but it actually had type IO (IO b)
.
This is confusing, but both the actual and expected type are wrong! The forM
statement should have been an IO action to print a bunch of filepaths, so it ought to have had type IO ()
.
Here's what's happened. In Haskell, the type of a do-block is the type of its last statement, and GHC has somehow decided that the whole outer do-block should have type FilePath -> IO ()
which is why it expects the last statement to have that type. Why does it think the outer do-block should have type FilePath -> IO ()
instead of IO ()
? Well, because you told it that printDirectory
should have type FilePath -> IO ()
and then bound the do-block directly to printDirectory
without giving printDirectory
an argument. You need to write printDirectory dir = do ...
, like so:
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"
Now the error message reads:
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 ...
When you see an IO xxx
versus IO (IO xxx)
mismatch cropping up in your code, it's usually because you've written a do-block statement as:
let x = something
when it should have been:
x <- something
Here, if we check the type of getCurrentDirectory >>= getDirectoryContents
in GHCi, we see it has the type:
> :t getCurrentDirectory >>= getDirectoryContents
getCurrentDirectory >>= getDirectoryContents :: IO [FilePath]
so it's an IO action to return a list of filepaths. However, we've assigned it with let
to filesInCurDir
. But we don't want filesInCurDir
to be an IO action, we want it to be the actual list of files. To do this, we need to use <-
in place of 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"
Now, we still have a type mismatch on the forM
statement, but we're getting closer:
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 expected forM
to have type IO ()
(i.e., an action that does some printing and returns nothing AKA "unit" AKA ()
), but instead forM
is trying to return a whole list of ()
. This happens when you use forM
(which builds a list to return) in place of forM_
(which just runs some IO actions for their side effects, like printing, but doesn't return anything itself). So, you need to replace forM
with forM_
, and you can now safely remove the "DEBUGGING" statement:
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"
It type checks with no errors!
Unfortunately, if you try to run it, it just goes into an endless loop, but that's because the recursion is broken. The directory contents include the special entries "."
and ".."
which you will want to skip but even if you fix that, the function doesn't actually ever change the current directory, so if there's at least one subdirectory, it'll just keep checking the current directory over and over.
So, I guess it's still debugging time!
Upvotes: 6