[Haskell-cafe] Monad-control rant

Edward Z. Yang ezyang at MIT.EDU
Sun Jan 29 17:55:24 CET 2012


Excerpts from Mikhail Vorozhtsov's message of Sun Jan 29 05:34:17 -0500 2012:
> You are trying to make bottoms a new null pointers. Sometimes it just 
> doesn't worth the effort (or depends on the interpreter you use). I want 
> to have the option to say: sorry, in this particular case (monad) I 
> don't distinguish `error` from non-termination, so `catch ⊥ h = ⊥`.

This is a longstanding complaint that Robert Harper has with lazy languages
(the "paucity of types" complaint.)

http://existentialtype.wordpress.com/2011/04/24/the-real-point-of-laziness/

There's not much I can say here, except that:

    - There really is no difference: GHC can sometimes detect nontermination
      and will throw an exception (for example, the deadlocked exception), and

    - The user will sometimes act as a termination checker, and ^C a program
      that is taking too long.

> I think it is one of the simplest layouts one can some up with. I'll try 
> to explain the motivation behind each inclusion.
> 
> ABORTS(μ) ⊆ RECOVERABLE_ZEROS(μ)

I'm sorry, I cannot understand the discussion below because you haven't
defined precisely what ABORTS means.  (See also below; I think it's
time to write something up.)

> Why are they not equal? After all we can always write `recover weird $ 
> \e → abort e`, right? But zeros from `RECOVERABLE_ZEROES \ ABORTS` may 
> have additional effects. For example, recoverable interruptions could 
> permanently disable blocking operations (you can close a socket but you 
> can't read/write from it). Why the inclusion is not the other way 
> around? Well, I find the possibility of `abort e1` and `abort e2` having 
> different semantics (vs `recover` or `finally`) terrifying. If you can 
> throw unrecoverable exceptions, you should have a different function for 
> that.
> 
> RECOVERABLE_ZEROS(μ) ⊆ FINALIZABLE_ZEROS(μ)
> 
> If a zero is recoverable, we can always "finalize" it (by 
> catch-and-rethrow).
>
> FINALIZABLE_ZEROS(μ) ⊆ ZEROS(μ)
> 
> This one is pretty obvious. One example of non-finalizable zeros is 
> bottoms in a non-MonadUnbottom monad (e.g. my X monad). Another would be 
> `System.Posix.Process.exitImmediately`.

Ugh, don't talk to me about the exit() syscall ;-)

> > If we can unify the semantics in a sensible way, I have no objection
> > (the choice of exceptions or pure values is merely an implementation
> > detail.)  But it's not obvious that this is the case, especially when
> > you want to vary the semantics in interesting ways.
> That's why I'm trying to make things like MonadUnbottom optional.

Well, I haven't actually checked if this works or not!

> >      - If the semantics are different, OK, now you need to write two catch
> >        functions, but you are handling each type of exception separately
> >        already, right?
> You have to handle IO exceptions only if you "leak" them from your 
> implementation. For transformer stacks it is always so, for some 
> interpreters it is not. The `ErrorT e IO` problem is related to another 
> can of worms: operation lifting through transformers.

OK.

> > IO has effects, so if I have mplus (effect>>  mzero) a, this equals
> > effect>>  a, not a.  Same applies for MaybeT IO.  I have to be very
> > careful to preserve the monoid property.  STM, on the other hand,
> > by definition has the ability to rollback. This is what makes it so nice!
> Should STM/`MaybeT IO` have MonadException instances? How `catch` and 
> `finally` will interact with `retry`/`MaybeT (return Nothing)`?

I don't see why not, as long as they obey the semantics.  But someone
should do the legwork here.

> >>> I also think that unrecoverable/recoverable exceptions is a legitimate idea.  I
> >>> think it could get its own typeclass, let's call it
> >>> MonadUnrecoverableException.  I don't think any MonadException is automatically
> >>> a MonadUnrecoverableException, by appealing to the laws of MonadException.
> >> I'm confused. What methods/laws would MonadUnrecoverableException contain?
> >
> > They'd be very simple! Unrecoverable exceptions always cause program execution
> > to "get stuck." There are no contexts (like catch) which affect them.
> So you are suggesting something like
> 
> class MonadUnrecoverableException μ where
>    throwUnrecoverable ∷ Exception e ⇒ e → μ α
> 
> But I'm not interested in throwing such exceptions! It may not even be 
> possible (allowed) to do that from within the monad itself (e.g. 
> external interruptions in my X monad). All I care about is that 
> unrecoverable zeros (not necessarily tied with Exception) exist, which 
> means that I cannot implement `finally` on top of `catch`.

Yes, but in that case, your semantics would have to change to add a case
for finally; you'd need to unwind the stack, etc etc.  You're talking about
finalizable, but unrecoverable exceptions.

> > Yes, I think for some this is the crux of the issue. Indeed, it is why
> > monad-control is so appealing, it dangles in front of us the hope that
> > we do, in fact, only need one API.
> >
> > But, if some functions in fact do need to be different between the two
> > cases, there's not much we can do, is there?
> Yes, but on the other hand I don't want to reimplement ones that are the 
> same. I want to have a modular solution precisely because it allows both 
> sharing and extensibility.

The cardinal sin of object oriented programming is building abstractions in
deference of code reuse, not the other way around.

Stepping back a moment, I think the most useful step for you is to write up
a description of your system, incorporating the information from this discussion,
and once we have the actual article in hand we can iterate from there.

Edward



More information about the Haskell-Cafe mailing list