Skip to content

Instantly share code, notes, and snippets.

@oxbowlakes
Last active April 15, 2018 20:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save oxbowlakes/b5c5de61fb6180e32b9591a19d9dc786 to your computer and use it in GitHub Desktop.
Save oxbowlakes/b5c5de61fb6180e32b9591a19d9dc786 to your computer and use it in GitHub Desktop.

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.

So what are the questions?

  1. In scalaz 8's IO[E, A], I still have two "modes" of failure (a raised E, or a thrown Throwable)?
  2. Presumably, I get the guarantee that resources are freed whichever is the failure mode (assuming they've been obtained through the proper abstraction)?
  3. if the failure mode is a thrown Throwable, is there a mechanism for getting at it (e.g. catchLeft, except)?
  4. What does recover (or recoverWith) look like, now that failure might be an E or a Throwable?
  5. Can we distinguish between an IO value which handles all thrown Exceptions internally, from one which doesn't by its type?

EDIT: answered questions

These are based on reading the README.md under the PR

  1. In scalaz 8's IO[E, A], I still have two "modes" of failure (a raised E, or a thrown Throwable)?

This is true

  1. 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 in MonadCatchIO

  1. 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 than IO.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. an IO[IOException, String] which actually encapsulates a thrown NullPointerException (as one example).

I think you can turn this into an IO[Throwable, IOException \/ A] via attempt and then turn this back into a IO[Throwable, A] (via absolve)

  1. What does recover (or recoverWith) look like, now that failure might be an E or a Throwable?

You cannot explicitly recover from the throwable failure mode, except via attempt/absolve

  1. Can we distinguish between an IO value which handles all thrown Exceptions 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

@jdegoes
Copy link

jdegoes commented Apr 15, 2018

In scalaz 8's IO[E, A], I still have two "modes" of failure (a raised E, or a thrown Throwable)?

An IO[E, A], when run, will either run forever, fail with E, compute an A, or be terminated for some Throwable.

Presumably, I get the guarantee that resources are freed whichever is the failure mode (assuming they've been obtained through the proper abstraction)?

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.

if the failure mode is a thrown Throwable, is there a mechanism for getting at it (e.g. catchLeft, except)?

Every fiber has a supervisor that will receive termination failures. The main fiber's supervisor is specified in the RTS, whereas fibers spawned by calling fork 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.

What does recover (or recoverWith) look like, now that failure might be an E or a Throwable?

E represents a known error type that is recoverable, and may be handled with attempt or its variants. However, fibers cannot recover from termination.

Can we distinguish between an IO value which handles all thrown Exceptions internally, from one which doesn't by its type?

IO[Void, A] versus IO[Exception, A], perhaps.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment