2

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 
4
  • Well, have you checked if your indentation is broken (i.e. you have both tabs and spaces)? Commented Dec 22, 2010 at 20:35
  • @delnan: I did. I even changed it from tabs to spaces. Commented Dec 22, 2010 at 20:38
  • @delnan: It's line 15:4: 'end <- hIsEOF handle' Commented Dec 22, 2010 at 20:41
  • use explicit { ; } to never have to worry about this whitespace foolishness. Commented Jun 15, 2018 at 8:08

4 Answers 4

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.

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

Comments

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.

3 Comments

I missed that openFile bit when skimming. Seems like you actually plugged it into a compiler :)
Thanks for your help. I think I need to read up on that dollar sign.
@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")).
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 

1 Comment

Even printFile could be shortened to printFile handle = do end <- hIsEof handle; unless end $ hGetLine handle >>= putStrLn >> printFile handle.
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.

2 Comments

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 thanks for the feedback, I expounded upon this point in the answer.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.