Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Handling exceptions thrown by pure code with `try`

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 prints 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?

like image 316
Eugene Loy Avatar asked Jul 05 '15 14:07

Eugene Loy


2 Answers

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.

like image 160
leftaroundabout Avatar answered Sep 22 '22 14:09

leftaroundabout


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.

like image 29
Reid Barton Avatar answered Sep 20 '22 14:09

Reid Barton