1

I'm trying to learn Haskell and want to write a small program which prints the content of a file to the screen. When I load it into GHCi I get the following error:

The last statement in a 'do' construct must be an expression

I know this question has be asked already here: Haskell — “The last statement in a 'do' construct must be an expression”.

Even though my code is very similar I still can't figure out the problem. If anyone could point out the problem to me I'd be very thankful.

module Main (main) where

import System.IO
import System(getArgs)

main :: IO()
main = do
    args <- getArgs
    inh <- openFile $ ReadMode head args
    printFile inh
    hClose inh

printFile :: Handle -> IO ()
printFile handle = do
    end <- hIsEOF handle
        if end
            then return ()
            else do line <- hGetLine handle
                putStrLn line
                printFile handle
Will Ness
  • 70,110
  • 9
  • 98
  • 181
Anonymous Coward
  • 856
  • 1
  • 11
  • 27

4 Answers4

5

Your indentation is broken. These are better:

printFile :: Handle -> IO ()
printFile handle = do
    end <- hIsEOF handle
    if end
        then return ()
        else do line <- hGetLine handle
                putStrLn line
                printFile handle

printFile :: Handle -> IO ()
printFile handle = do
    end <- hIsEOF handle
    if end
        then return ()
        else do
            line <- hGetLine handle
            putStrLn line
            printFile handle

By having if further indented than end <- hIsEof handle, it was actually a line continuation, not a subsequent action in the do. Similarly, the fact that you had putStrLn line less indented than line <- hGetLine handle means that the do (inside the else) ended there.

ephemient
  • 198,619
  • 38
  • 280
  • 391
4

There are seveal issues. First, the if is indented too far - end <- ... is assumed to be the last line of the do. Unindent...

next issue comes up. Same error message, only at line 18. This time, line 19 and 20 are not indented deeply enough (they aren't parsed as part of the do). Indent (looks nicer anyway, since it all lines up now)... next error message. The good news is, it's not an indentation error this time and the fix is again trivial.

test.hs:9:22:
    Couldn't match expected type `([a] -> a) -> [String] -> FilePath'
           against inferred type `IOMode'
    In the second argument of `($)', namely `ReadMode head args'
    In a stmt of a 'do' expression:
        inh <- openFile $ ReadMode head args
    In the expression:
        do { args <- getArgs;
             inh <- openFile $ ReadMode head args;
             printFile inh;
             hClose inh }

The fix is inh <- openFile (head args) ReadMode. If you want a more detailed explanation of why/how your version is incorrect, or what the error means, let me know and I'll edit.

  • I missed that `openFile` bit when skimming. Seems like you actually plugged it into a compiler :) – ephemient Dec 22 '10 at 21:07
  • Thanks for your help. I think I need to read up on that dollar sign. – Anonymous Coward Dec 22 '10 at 21:17
  • 1
    @Anonymous: It's nothing fancy, in fact it's boringly trivial :) `f $ x = f x`. It's only useful because it has very low priority, so it can replace parens in some situations (i.e. `return $ Foo 1 "bar"` instead of `return (Foo 1 "bar")`). –  Dec 22 '10 at 21:22
1

You wrote this:

main :: IO()
main = do
    args <- getArgs
    inh <- openFile $ ReadMode head args
    printFile inh
    hClose inh

But it is probably nicer like this:

main :: IO()
main = do
    args <- getArgs
    withFile (head args) ReadMode printFile
Robert Massaioli
  • 13,379
  • 7
  • 57
  • 73
  • Even `printFile` could be shortened to `printFile handle = do end <- hIsEof handle; unless end $ hGetLine handle >>= putStrLn >> printFile handle`. – ephemient Dec 23 '10 at 15:07
1

You can always use explicit bracketing with { ; } to never have to worry about this whitespace foolishness.

printFile :: Handle -> IO ()
printFile handle = do {
    end <- hIsEOF handle ;
        if end
            then return ()
            else do { line <- hGetLine handle ;
                putStrLn line ;
                printFile handle }}

would have been totally fine (as in, not cause the error).

I/O is dealt with through the special "do" language, in Haskell. It should be embraced. That it is actually implemented via monads is an implementational detail.

To clarify: I don't think braces are better, I think they should go together with a nice and consistent indentation. Braces give us nice and immediate visual clues as to the code's structure. Wild indentation will of course be a pointless distraction most of the time. But also, braces give us a guarantee for the working code, and relieve us from the pointless worries of whitespace accidents. They remove this brittleness.

Will Ness
  • 70,110
  • 9
  • 98
  • 181
  • Seriously? Even if you think braces are better than indentation – that's a minority opinion in the Haskell community. So saying “always use bracketing” is pretty much flamebait. You can say something like, “it's a good idea to try explicit bracketing if you're not completely confident with indentation and getting strange errors, in fact I prefer to always bracket/semicolon my `do` constructs”. – leftaroundabout Jun 15 '18 at 10:32
  • @leftaroundabout thanks for the feedback, I expounded upon this point in the answer. – Will Ness Jun 15 '18 at 10:41