[Haskell-cafe] Catching string from error function with GHC Control.Exception

Cale Gibbard cgibbard at gmail.com
Sun Jan 1 14:46:15 EST 2006

On 01/01/06, Andrew Savige <ajsavige at yahoo.com.au> wrote:
> I am a Haskell beginner, using GHC 6.4.1.
> Suppose I have a function, tt, say, which may call error.
> For the purposes of testing tt, I'd like to be able to catch
> the error string and assert that it matches the expected error
> string. Is HUnit or QuickCheck (or either) the appropriate tool
> for this sort of testing?
> I managed to get it working with HUnit, but it's horribly kludgy
> mainly because I'm yet to grok monads. The little test program
> below demonstrates my question outside of HUnit:
> import Control.Exception
> tt :: String -> Int
> tt s = error ("hi error '" ++ s ++ "'")
> -- tt s = read s
> ttWrap :: String -> IO String
> ttWrap x = do let r = tt x
>               -- comment out next line, error string not caught
>               putStr (show r)
>               return (show r)
> ttWrap2 :: IO String
> ttWrap2 = do res <- tryJust errorCalls (ttWrap "123")
>              case res of
>                Right r  -> return (show r)
>                Left  r  -> return r
> main = do x <- ttWrap2
>           putStrLn ("caught:" ++ x)
>           y <- ttWrap2
>           putStrLn ("caught:" ++ y)
> When you run the above test program, it does indeed catch the
> error string thrown by the tt function (and prints it out in
> main). However, if you comment out the putStr line as shown in
> the comment above, it no longer catches the error string, but
> dies instead. Why?

The error never happens until r (which is tt x) is actually evaluated.
Applying putStr to (show r) will certainly demand the evaluation
because it will be needed for IO, but nothing about return (show r)
forces this evaluation to occur -- an unevaluated thunk will be
returned. So the error doesn't happen and so doesn't get caught. The
Right branch of the case is taken, which still doesn't cause a
failure. The failure occurs when the value gets back to main and you
try to print it.

So you need to ensure the evaluation of r occurs in a particular order
relative to IO. Control.Exception.evaluate does this -- it forces its
parameter to be evaluated up to the top level constructor before IO
proceeds, which should cause the error to occur and be caught. Note
that if tt returned some more complicated structure, with the error
hidden deep in the structure, you might need to use seq recursively,
or just one of the DeepSeq-type libraries out there. (GHC includes
Control.Parallel.Strategies which takes care of this, though it's not
well documented in the libraries. myStructure `using` rnf will do the
same as deepSeq does.)

Hope this helps,
 - Cale

More information about the Haskell-Cafe mailing list