2

I'm pretty new to Haskell, and want to execute recursion in do block.

countLines :: String -> IO Int
countLines filePath = do
    isFile <- doesFileExist filePath
    if isFile
        then do contents <- readFile filePath
                print contents
                pure 0
        else do files <- getDirectoryContents filePath    
                [countLines(file) | file <- files] -- recursion here!
                pure 0

until i add this list comprehension, everything works fine but once i add this, i get the following error:

Main.hs:59:17: error:
    • Couldn't match type ‘[]’ with ‘IO’
      Expected type: IO (IO Int)
        Actual type: [IO Int]
    • In a stmt of a 'do' block: [countLines (file) | file <- files]
      In the expression:
        do files <- getDirectoryContents filePath
           [countLines (file) | file <- files]
           pure 0
      In a stmt of a 'do' block:
        if isFile then
            do contents <- readFile filePath
               print contents
               pure 0
        else
            do files <- getDirectoryContents filePath
               [countLines (file) | file <- files]
               pure 0
   |
59 |                 [countLines(file) | file <- files]
   |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
bash-3.2$ 

does any one know how to fix this?

1
  • You can convert a list of actions (like [ countFile(file) | file <- files ]) into an action that executes them all using the sequence function Commented May 2, 2020 at 3:27

2 Answers 2

2

The list comprehension is an expression of the [IO Int], not IO Int, while a list [] is a member of the Monad typeclass, it is not the one you are currently working with in the do block. You can use mapM_ :: (Foldable f, Monad m) => (a -> m b) -> f a -> m () to work with recursion here:

countLines :: String -> IO Int
countLines filePath = do
    isFile <- doesFileExist filePath
    if isFile
        then readFile filePath >>= print
        else getDirectoryContents filePath >>= mapM_ countLines
    pure 0

As @chi says, if you need to retrieve a list of the results, you should use mapM :: (Traversable f, Monad m) => (a -> m b) -> t a -> m (t b). This thus can you provide a list of the results of the monadic calls. You can for example sum these up to obtain the result. I leave that as an exercise.

Sign up to request clarification or add additional context in comments.

1 Comment

I guess the OP actually wants mapM (or traverse / for) since they'll really need the returned Ints from the recursive calls.
2

You're in the IO monad, so every line should produce an IO a, but your list comprehension produces... well, a list [a]. Thankfully, there is a function sequence_ :: (Foldable t, Monad m) => t (m a) -> m (), that will turn your [IO ()] into a single IO (), while running all of the IO actions in it. Thus your else block should look like this instead:

    else do files <- getDirectoryContents filePath    
            sequence_ [countLines(file) | file <- files]
            pure 0

As Willem mentions, it's possible to simplify these even more using mapM_ :: (Foldable f, Monad m) => (a -> m b) -> f a -> m ():

    else do files <- getDirectoryContents filePath    
            mapM_ countLines files
            pure 0

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.