[Haskell-cafe] Doubting Haskell

Cale Gibbard cgibbard at gmail.com
Sat Feb 16 17:53:50 EST 2008


On 16/02/2008, Alan Carter <alangcarter at gmail.com> wrote:
> Greetings Haskellers,
>
> I'm still hoping that this is solvable. That the instinctive
> priorities of production programmers are just different to those of
> researchers, and in fact it *is* possible to open a file *and* read
> it, checking *both* error returns, without being driven insane. If so,
> I sincerely suggest an example or two, like the small but well formed
> programs in K&R, Stroustrup or Gosling saying things like:
>
>   if((fp = fopen(...)) != NULL)
>   {
>     if(fgets(...) != NULL)
>     {
>       printf(...);
>     }
>
>     fclose(...)
>   }
>
> Best wishes - and still hoping I'm wrong after all
>
> Alan Carter

Well, first of all, have you read the documentation for System.IO?

http://www.haskell.org/ghc/docs/latest/html/libraries/base/System-IO.html

That has all the corresponding functions you need. I'm not sure I
understand completely how you managed to spend two weeks struggling
with this before asking. Two minutes on #haskell, or a quick question
about how to open and read a file should have got you a useful
response. :)

First, I'll write the program in a straightforward, but extremely
explicit manner, handling possible errors and managing clean up
explicitly. This code is rather verbose, so I'll then show some other
less verbose ways to handle things while still maintaining safety.

So, the first version:

import System.IO
import Control.Exception (try)

main = do mfh <- try (openFile "myFile" ReadMode)
          case mfh of
            Left err -> do putStr "Error opening file for reading: "
                           print err
            Right fh ->
                do mline <- try (hGetLine fh)
                   case mline of
                     Left err -> do putStr "Error reading line: "
                                    print err
                                    hClose fh
                     Right line -> putStrLn ("Read: " ++ line)

Okay, so this is hopefully fairly self-explanatory to a C-programmer.
The only potentially-confusing part is the function 'try', imported
from Control.Exception. What it does is to catch all possible
exceptions, and reflect them through the return value of the action.
If an exception is thrown, 'try' will catch it, and give us a value of
the form (Left e), for e being the exception. If instead, the
operation succeeds without an exception, we get a value (Right x),
where x is the normal return value of the action.

The successive 'case' expressions are used to pattern match on this,
and handle the errors by printing out an explanatory message. Some
example runs of this program:

cale at zaphod:~$ rm myFile
cale at zaphod:~$ ./read
Error opening file for reading: myFile: openFile: does not exist (No
such file or directory)
cale at zaphod:~$ touch myFile
cale at zaphod:~$ ./read
Error reading line: myFile: hGetLine: end of file
cale at zaphod:~$ echo "hello" >> myFile
cale at zaphod:~$ ./read
Read: hello

This program actually does more error handling than your example C
program, so let's tone it down a bit, and make use of some nice IO
operations provided to handle errors and clean things up safely in the
event of a failure.

import System.IO

main = withFile "myFile" ReadMode $ \fh ->
         do line <- hGetLine fh
            putStrLn ("Read: " ++ line)

The function 'withFile' takes a filename, a mode in which to open the
file, and a function, taking a filehandle, and giving an action to be
performed with that handle, and wraps that action up inside an
exception handler, which ensures that the file handle is safely closed
if an exception is thrown. (This doesn't matter much in our small
example, but I'm sure you'll appreciate how that's an important
thing.)

We don't handle the exceptions explicitly in this program, but we
still could. There are a host of exception-handling mechanisms in
Control.Exception, ranging from simple value-oriented things like try,
to more explicit operations for wrapping things in exception handlers,
like catch:

catch :: IO a -> (Exception -> IO a) -> IO a

Or to get more selective:

catchJust :: (Exception -> Maybe b) -> IO a -> (b -> IO a) -> IO a

Which takes a function that gets to decide whether to handle the
exception, and at the same time, transform the exception in some way
before passing it on to the exception handler.

For more information about exceptions, check out the documentation for
Control.Exception here:

http://www.haskell.org/ghc/docs/latest/html/libraries/base/Control-Exception.html

I assure you that Haskell is a very reasonable programming language in
which to write safe and correct programs. There are whole companies
founded on writing high-assurance software in Haskell.

If you have more questions, I would be happy to answer them, either
here, or perhaps more comfortably, on IRC, #haskell on
irc.freenode.net. It's a very beginner friendly channel, and asking
questions there is a great way to learn the language quickly, and find
the resources you need.

Hope this helps!
 - Cale


More information about the Haskell-Cafe mailing list