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.
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
.