
On Sun, Oct 17, 2010 at 8:04 PM, Arie Peterson
On Thu, 14 Oct 2010 12:01:59 +0200, Michael Snoyman
wrote: [...] which I believe is a flawed design in the MonadCatchIO-transformers package. Here are my thoughts on this and what I think needs to be done to fix it.
[...]
Try running the code with each version of go uncommented. In the first two, "sequel called" gets printed. However, in the third, it does not. The reason is short-circuiting: if we remember from the definition of finally, there are two cases we account for. If an exception is called, catch addresses it. If not, we assume that the next line will be called. However, in the presence of short-circuiting monads like ErrorT, that line of code will never get called!
Yes. That is the behaviour I would expect.
There are two kinds of exceptional values in, for instance, 'ErrorT e IO a': • IO exceptions, in the "underlying monad" 'IO'; • error values of type 'e', in the monad transformer 'ErrorT e'. The MonadCatchIO instance for ErrorT deals with the first kind only. Catching IO exceptions, and cleaning up after them, is what MonadCatchIO was invented for. I feel that I should not decide for all users how these two layers of exceptions should interact; keeping the MonadCatchIO instance oblivious to the underlying monad as much as possible seems like the safest/most general thing to do.
Meanwhile, I can see why you would want 'finally' to also catch the ErrorT errors, in your example, and circumvent the short-circuiting. However, I'm not convinced that this is always the right (expected, most useful, ...) behaviour. Maybe I just need more convincing :-).
I can't think of a single use case where the current behavior is desired. On the other hand, many common cases would require the semantics I'm looking for: * My use case: returning a resource to a resource pool upon completion of an action. * Freeing allocated memory (probably better to use bracket there, but the same issue exists for that function). * Closing a file handle at the end of an action (again, a good use case for bracket). While we're on the topic, I also found a problem with the ContT instance of MonadCatchIO which resulted in double-freeing of memory[1]. I understand from Oleg's comments earlier in this thread that this is an expected behavior of the ContT monad, but I *don't* think it's an expected behavior of the bracket_ function. If there's no way to define a MonadCatchIO instance of ContT that only calls the cleanup code once, perhaps it doesn't make sense to define that instance at all.
By the way: my apologies for not being more responsive and proactive in this matter. At the moment, I have very little time for my haskell endeavours. But more importantly, my maintainership of MonadCatchIO-transformers is mostly coincidental. (I found the MonadCatchIO-mtl code very useful, except I needed it for 'transformers' instead of 'mtl'. So, I forked it (at that time, only the cabal file needed editing), and put it on hackage.)
I hadn't realized that you weren't the maintainer of MonadCatchIO-mtl (I simply never looked). I've CCed him on this issue as well.
If you feel that as maintainer I'm more of a roadblock than helping you make effective use of this library, then perhaps you should take over maintainership.
Thank you for the offer, but I don't think I'm in a position to take over maintainership of another library. However, I think that my original suggestion of moving all of the exception-handling functions into the type class itself would solve the current issue; is there a reason not to do so? I'm still not sure what to do about ContT. Michael [1] http://www.mail-archive.com/haskell-cafe@haskell.org/msg77183.html