I am playing with exceptions in haskell and stumbled upon one thing I can't understand yet.
In GHCi I do:
Prelude Control.Exception> let thrower = (read "A") :: Int
Prelude Control.Exception> :{
Prelude Control.Exception| let main = do
Prelude Control.Exception| x <- (try $ return thrower) :: IO (Either SomeException Int)
Prelude Control.Exception| print x
Prelude Control.Exception| :}
Prelude Control.Exception> main
This defines thrower
, my test expression that will fail with exception.
Then I define main
that wraps that expression into try
(wrapping it into IO
first, since try
accepts IO
) and then unwraps it from IO
(produced by try
) and print
s it.
Everything looks great so far - evaluating main
in repl gives back exception wrapped into Either:
Right *** Exception: Prelude.read: no parse
However, if I try to compile and execute same code as an app:
module Main where
import Control.Exception
thrower = (read "A") :: Int
main = do
x <- (try $ return thrower) :: IO (Either SomeException Int)
print x
... it gets crashed with exception:
haskelltest.exe: Prelude.read: no parse
It seems like exception slipped past try
.
What am I missing here and what is the correct way to handle this?
Well, basically (as Sebastian Redl pointed out earlier) this is a strictness issue. return thrower
does not in any way evaluate thrower
, so try
succeeds. Only when the content of the Either SomeException Int
is printed, namely Right thrower
, does read
actually try to parse "A"
, and fails... but at this point, the try
is already over.
The way to prevent this is to inject the parse result strictly into the IO
monad, with
main = do
x <- try $ evaluate thrower :: IO (Either SomeException Int)
print x
Why the try
fails with your code in GHCi I don't know; I daresay it shouldn't. Aha: as Reid noted, it doesn't fail actually!
Arguably, this is an example for why exceptions should generally be avoided in Haskell. Use a suitable monad transformer to make it explicit what errors might occur, and to get reliable evaluation of the error-checking.
Part of what you're missing is that when you ran your code in ghci, try
also did not catch the error raised by read "A" :: Int
. Weren't you expecting a Left <something>
result? See leftaroundabout's answer for why that is.
The difference between ghci and ghc here is probably due to output buffering: output to stdout (like "Right "
here) is unbuffered in ghci, but line buffered by default in a compiled program.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With