Probably unneeded returns
For your argc &lt;- 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 &lt;- 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:


First element (head)
Last element (last)
Everything but the first (tail)
Everything but the last (init)


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

main = do
    args &lt;- 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 &lt;- 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 &gt;&gt;= operator by doing

main = getArgs &gt;&gt;= \args -&gt; 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 &gt;&gt;= (bind) operator takes that IO value, extracts the value inside, and sends it into the function on the right side of the &gt;&gt;= operator, where it gets bound to the argument args.