Take the 2-minute tour ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

This is my first "useful" Haskell program. It's invoked with filenames as arguments where the last filename is the destination to which the content of all other files will be concatenated. As far as I can tell it's working but I don't like the two returns in the main function and would like to know how I can get rid of the do in the concatFiles function.

import System.Environment (getArgs)
import System.IO (hClose, hPutStr, openFile, IOMode(WriteMode))


concatFiles :: FilePath -> [FilePath] -> IO ()
concatFiles destination files = do
    writeHandle <- openFile destination WriteMode
    mapM (writeToDestination writeHandle) files
    hClose writeHandle
    where
        writeToDestination writeHandle file =
            hPutStr writeHandle =<< readFile file

main = do
    args <- getArgs
    argc <- return $ length args
    destination <- return $ last args
    concatFiles destination $ take (argc-1) args
share|improve this question

1 Answer 1

up vote 10 down vote accepted

For your argc <- return $ length args , the reason the compiler is telling you to use the return is because in do notation, monadic binding lines must be the form:

binding <- value wrapped in whatever monad you're using

So length args just returns an Int, but Haskell wants an IO Int. By doing return $ length args, you lift the value returned by length args into the IO monad.

You are correct in smelling something funny here. Haskell provides a much cleaner way of assigning non-monadic values to be used inside of do notation with the let keyword.

You can replace those two lines with:

let argc = length args
    destination = last args

Note that bindings must have the same indentation level within a let assignment block.

But really, there is more to improve! In Haskell, the four most common list partitions are:

  1. First element (head)
  2. Last element (last)
  3. Everything but the first (tail)
  4. Everything but the last (init)

So with init we can eliminate the need for argc, changing your main to

main = do
    args <- getArgs
    let destination = last args
    concatFiles destination $ init args

But really, the word destination is longer than the function call last args, and doesn't really add much clarity, so we can change that, too, removing the need for the let entirely!

main = do
    args <- getArgs
    concatFiles (last args) (init args)

And if you really wanted to become more intimately familiar with how do notations and monads in general work, we can actually remove the do notation and replace it with a >>= operator by doing

main = getArgs >>= \args -> concatFiles (last args) (init args)

So as a quick recap of what is happening here, we are calling getArgs, which returns a list of arguments wrapped in the IO monad. The >>= (bind) operator takes that IO value, extracts the value inside, and sends it into the function on the right side of the >>= operator, where it gets bound to the argument args.

share|improve this answer
3  
If you're going to get fancy by using >>=, you may as well get rid of the lambda as well! main = getArgs >>= concatFiles <$> last <*> init. –  amalloy Mar 20 at 6:57
    
@amalloy exactly what I was thinking –  awashburn Apr 27 at 19:46

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.