Wanabe Super Coder
Wanabe Super Coder

Reputation: 185

Errors when running program

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

Answers (1)

K. A. Buhr
K. A. Buhr

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 expression doesDirectoryExist fry
  • it EXPECTED to find a FilePath (because the (</>) operator's first argument is obviously supposed to be a FilePath)
  • 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 to doesDirectoryExist)
  • it expected a FilePath
  • but it actually found a [FilePath]

This is strange! We expected a FilePath, too, not a whole list of FilePaths. 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 frys 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

Related Questions