A somewhat surprising catch

Let’s get straight to it. Here’s an example of something that I found somewhat surprising in Haskell. First a bit of setup:

printNum n = putStrLn $ "num: " ++ (show n)

handleE e = do
    putStrLn $ "Caught something: " ++ (show e)
    return (-1)

errorP = fromMaybe (error "Got Nothing")

And here it is, in ghci:

> CE.catch (return $ errorP Nothing ) handleE >>= printNum
num: *** Exception: Got Nothing

And to show that things are lazy we change handleE:

handleE e = do
    putStrLn $ "Caught something: " ++ (show e)
    return []

Then we can map errorP onto a list like this:

> CE.catch (return $ map errorP [Just 17, Just 42, Nothing, Just 666]) handleE
[17,42,*** Exception: Got Nothing

In neither case I saw the behaviour I was expecting. A chat on IRC showed that others see this as natural behaviour, explained by laziness. It wasn’t until a night’s sleep that I realised that there still was something that bothered me about that explanation. Another explanation would be that catch isn’t special. At first I didn’t realise I expected it to be special; I expected it to somehow wrap the evaluation of its first argument so that no matter when it was evaluated any exception raised would be caught. As far as I can see this would be no small feat if laziness is to be preserved. It would require catch to be special.

So, what does this mean in practice? Well, here are my thoughts. One needs to think carefully about where using catch makes sense in a program. It has to be inside IO, a well-documented fact, but it also has to cover something that isn’t lazy since it then will have no effect at all. My gut feeling is that catch is useful “in the large”, with that I mean as a sort of catch-all “high up” in the program, e.g. in main. That means its usefulness in libraries is limited (except for IO heavy libs, like FFI bindings to C), it should also probably not be used like try-catch often is used in Python.

I’ve since taken a look at the ErrorT monad transformer and it seems it behaves like I expected catch to. That’s for another post though.

Leave a comment