I have a monad transformer stack that looks like this (for some user-defined types R
, S
and E
):
EitherT[RWST[IO, R, Unit, S, ?], E, ?]
I used to be very rigorous when using this, in wrapping Java calls such that any thrown exceptions would be caught at turned into an E
of my choosing. This kind of thing:
MyStack.fromTryCatch( javaApi.foo(), t => FooFailed(t) )
Over a (not very long) period, I realied that I was on a hiding to nothing: I would inevitably miss wrapping some calls and, even more inevitably, these calls would occasionally fail by throwing exceptions [1]. These would bubble up out of my stack and ... DISAPPEAR!
That's fine, though. "Let it crash" and all that. Except that often the programs would not crash. Because exceptions had bubbled out of my stack, resources were not freed and non-daemon threads would be hanging around, the rest of my program clinging on for dear life. There may be no exceptions in the logs, nothing! How could there be, my program having failed in a mode which I wished to make impossible?
Whilst I still use the stack, I'm now much less concerned about turning all thrown exceptions into an E
, and much more concerned that the IO[Unit]
that represents the whole program has exception-handling added at the top level: to log any errors and cleanly exit.
But this top-level exception-handling is not represented by the type; an IO[Unit]
which catches all exceptions internally, - logging them and invoking System.exit(-1)
- looks just the same as one which doesn't.
- In scalaz 8's
IO[E, A]
, I still have two "modes" of failure (a raisedE
, or a thrownThrowable
)? - Presumably, I get the guarantee that resources are freed whichever is the failure mode (assuming they've been obtained through the proper abstraction)?
- if the failure mode is a thrown
Throwable
, is there a mechanism for getting at it (e.g.catchLeft
,except
)? - What does
recover
(orrecoverWith
) look like, now that failure might be anE
or aThrowable
? - Can we distinguish between an IO value which handles all thrown
Exception
s internally, from one which doesn't by its type?
These are based on reading the README.md under the PR
- In scalaz 8's
IO[E, A]
, I still have two "modes" of failure (a raisedE
, or a thrownThrowable
)?
This is true
- Presumably, I get the guarantee that resources are freed whichever is the failure mode (assuming they've been obtained through the proper abstraction)?
This is true - via the bracket abstraction. Note that
EitherT[IO, E, ?]
can be made to guarantee similar, via some shenanigans inMonadCatchIO
- if the failure mode is a thrown
Throwable
, is there a mechanism for getting at it (e.g.catchLeft
,except
)?
If the code which throws an exception is created via a call to
IO.sync
rather thanIO.syncThrowable
(which is basically my argument, based on my experience, that this is inevitable without compiler help to prevent you from doing it), then you can end up with e.g. anIO[IOException, String]
which actually encapsulates a thrownNullPointerException
(as one example).
I think you can turn this into an
IO[Throwable, IOException \/ A]
viaattempt
and then turn this back into aIO[Throwable, A]
(viaabsolve
)
- What does
recover
(orrecoverWith
) look like, now that failure might be anE
or aThrowable
?
You cannot explicitly recover from the throwable failure mode, except via
attempt
/absolve
- Can we distinguish between an IO value which handles all thrown
Exception
s internally, from one which doesn't by its type?
I don't know
[1] - note that in Scala, you get no compiler help that a checked exception might be thrown
An
IO[E, A]
, when run, will either run forever, fail withE
, compute anA
, or be terminated for someThrowable
.If you use
bracket
to describe resource allocation and release, you get the guarantee that all resources will be freed, regardless of whether a computation fails or is terminated.Every fiber has a supervisor that will receive termination failures. The main fiber's supervisor is specified in the
RTS
, whereas fibers spawned by callingfork
can have a custom supervisor specified (or merely inherit the parent's supervisor).By default, all termination failures have their stack traces dumped to standard output.
There is no way to lose exceptions and because of the hierarchical nature of supervisors, exception-handling logic can be modular and consolidated.
E
represents a known error type that is recoverable, and may be handled withattempt
or its variants. However, fibers cannot recover from termination.IO[Void, A]
versusIO[Exception, A]
, perhaps.