haskellexceptionfunctional-programmingpurely-functionalio-monad

Exception handling and purity in Haskell


In The acquire-use-release cycle section from Real World Haskell, the type of bracket is shown:

ghci> :type bracket
bracket :: IO a -> (a -> IO b) -> (a -> IO c) -> IO c

Now, from the description of bracket, I understand that an exception might be thrown while the function of type a -> IO c is running. With reference to the book, this exception is caught by the calling function, via handle:

getFileSize path = handle (\_ -> return Nothing) $
  bracket (openFile path ReadMode) hClose $ \h -> do
    size <- hFileSize h
    return (Just size)

I can't help but thinking that when the exception does occur from within bracket's 3rd argument, bracket is not returning an IO c.

How does this go well with purity?

I think the answer might be exactly this, but I'm not sure.


Solution

  • I can't help but thinking that when the exception does occur from within bracket's 3rd argument, bracket is not returning an IO c.

    Prelude> fail "gotcha" :: IO Bool
    *** Exception: user error (gotcha)
    

    As you note, no Bool (respectively, c) value is produced. That's ok, because the action does not conclude – instead it re-raises the exception. That exception might then either crash the program, or it might be caught again somewhere else in calling code – importantly, whoever catches it will a) not get the result value (“the c”), you never do that in case of an exception; b) doesn't need to worry about closing the file handle, because that has already been done by bracket.