All Projects β†’ expede β†’ rescue

expede / rescue

Licence: Apache-2.0 License
πŸš’βœ¨ Rescue: better errors through types (a more type directed MonadThrow/MonadCatch)

Programming Languages

haskell
3896 projects
Nix
1067 projects

Projects that are alternatives of or similar to rescue

babel-errors
Nicer error messages for Babel
Stars: ✭ 15 (-16.67%)
Mutual labels:  error-handling
failure
Error management
Stars: ✭ 1,448 (+7944.44%)
Mutual labels:  error-handling
nuxt-winston-log
Nuxt module for logging SSR errors using winston
Stars: ✭ 41 (+127.78%)
Mutual labels:  error-handling
custom-exception-middleware
Middleware to catch custom or accidental exceptions
Stars: ✭ 30 (+66.67%)
Mutual labels:  error-handling
superagent-intercept
Add functions that will be called during end() e.g. for handling error conditions without having the same code all over the place.
Stars: ✭ 23 (+27.78%)
Mutual labels:  error-handling
progress-bar-log
A component to display a progress bar and last X logs at the same time.
Stars: ✭ 44 (+144.44%)
Mutual labels:  error-handling
friendly-error
Show uncaught errors friendly in Node.js.
Stars: ✭ 75 (+316.67%)
Mutual labels:  error-handling
belay
Robust error-handling for Kotlin and Android
Stars: ✭ 35 (+94.44%)
Mutual labels:  error-handling
react-error-guard
βš›οΈAn overlay for displaying stack frames based on create-react-app/packages/react-error-overlay
Stars: ✭ 18 (+0%)
Mutual labels:  error-handling
merr
πŸ”₯ Minimal and good enough error handling library for Clojure/ClojureScript
Stars: ✭ 25 (+38.89%)
Mutual labels:  error-handling
static-404
⚑️ A WordPress plugin to quickly send a 404 for missing static files
Stars: ✭ 24 (+33.33%)
Mutual labels:  error-handling
result17
A rust like Result type for modern C++
Stars: ✭ 13 (-27.78%)
Mutual labels:  error-handling
miette
Fancy upgrade to std::error::Error.
Stars: ✭ 945 (+5150%)
Mutual labels:  error-handling
retryx
Promise-based retry workflow library.
Stars: ✭ 21 (+16.67%)
Mutual labels:  error-handling
sentry-testkit
A Sentry plugin to allow Sentry report interception and further inspection of the data being sent
Stars: ✭ 78 (+333.33%)
Mutual labels:  error-handling
safe
πŸ›‘ PHP functions smarten up to throw exceptions instead of returning false or triggering errors.
Stars: ✭ 15 (-16.67%)
Mutual labels:  error-handling
failure
An error handling package for Go.
Stars: ✭ 24 (+33.33%)
Mutual labels:  error-handling
gommon
A collection of common util libraries for Go
Stars: ✭ 26 (+44.44%)
Mutual labels:  error-handling
dx
JavaScript without `try...catch`.
Stars: ✭ 26 (+44.44%)
Mutual labels:  error-handling
fejl
Error-making utility for Node apps.
Stars: ✭ 30 (+66.67%)
Mutual labels:  error-handling

πŸš’βœ¨ Rescue

More Understandable Error Handling

Continuous Integration License

rescue is an experimental error handling library for Haskell.

The standard library approach to error handling is to use an existential type to subclass any error as SomeException. This is very convenient (requires nealy zero set up), and matches what developers expect coming from other ecosystems.

A core goal of rescue is to give the programmer clarity about which execptions are possible at any given point in the code. We achieve this by using (open) variants for compositon rather than inheritance. We hide the detail as much as posisble, and attempt to make this work in a flexible, constraint-driven style as much as possible.

Perhaps there is some value in treating these more separately. There is an old distinction that mirrors the above as errors (irrecoverable) vs exceptions (recoverable).

rescue breaks the problem into synchronous and asynchoronous exceptions. This distinction allows us to express our assumptions about the error environment, separate modes of handling for the same monadic stack, and even different errors.

Goals

Our goals are to have:

  • Clarity of what errors can be raised by some code
  • Not need a large tree of error types for our application (set-like)
  • Way to declare assumptions about which errrors are available
    • e.g. "can raise X, Y, and Z", or "raises exactly X and Y but none others"
  • Extensible
  • Ability to call code that raises some subset of errors
  • Handle (and eliminate) exceptions from the context

Approach

The closest approach is MonadError, except that our implementation uses type-level lists for flexiblity, and hides the exception parameter from the class constraint as a type family. rescue also splits raise and attempt into separate classes to help us more granularly express the effects available in the current context.

Class Heirarchy

  1. MonadRaise -- roughly MonadThrow
  2. MonadRescue -- roughly MonadCatch
  3. MonadCleanup -- roughly MonadBracket

Monad m => MonadRaise m => MonadRescue m => MonadCleanup m

Errors / Asynchronous Exceptions

An asynchronous error (e.g. coming from another thread) comes with a lot more uncertainty. We may not be aware of the type of error being thrown to us, or when it's thrown (since the runtime will interrupt). The purpose of this scenraio is typically to cleanup some resource and immeditely rethrow.

While a more structured, type-driven style would be appreciated here, this is fundamentally how the runtime works. As such, we do need to contend with SomeAsyncExceptions.

The good news is that we're not expected to do much with them. Essentially all we care about is:

  • That an async exception interrupted our flow,
  • We should not attempt recovery
  • The error should be rethrown

Cleanup is also a use case for synchronous exceptions. If something is thrown inside a context with an open resource, we should clean that up.

The basic flow for an async execption is then:

  1. An async exception is raised
  2. Convert it to a synchronous exception
  3. Run the normal cleanup
  4. Rethrow the original asynchronous error

This means that any instacne of MonadAsyncCleanup needs a MonadCleanup and Raises m SomeAsyncException

FAQ

Why another typeclass?

It's true that MonadThrow is totally pervasive. However, not trying to

There's also nothing stopping us from writing a function of the type

fromThrow :: (MonadCatch m, MonadRaise n) => m a -> n a

...though the conversion from SomeException would take a bit of care.

Why avoid SomeException?

SomeException is typesafe, but very difficult to track by hand. In fact, with async exceptions, you may need to handle an error interrupting your execution even if you don't have a MonadThrow in your context.

By treating exceptions as something visible and tarckable (though hidden when you don't need it), we gain a lot of ability to reason about our program, and avoid writing lots of nested Eithers.

Does this do async exception handling?

It does! MonadCleanup is the typeclass, and it has very few instances. It's essentially MonadBracket, but with explicit errors.

This separation of the SomeException and OpenUnion can help determine the intention of the exception. SomeException (and SomeAsyncException) are really more like errors -- things that should fail and not be recovered from. In a world with async exceptions, the cleanest way to respond to an error is to stop our execution, cleanup any resources, and propogate the error.

Are there other packages attempting to solve this problem?

Yep! For instance, safe-exceptions. They distinguish between three kinds of exceptions:

We're going to define three different versions of exceptions. Note that these definitions are based on how the exception is thrown, not based on what the exception itself is:

Synchronous exceptions are generated by the current thread. What's important about these is that we generally want to be able to recover from them. For example, if you try to read from a file, and the file doesn't exist, you may wish to use some default value instead of having your program exit, or perhaps prompt the user for a different file location.

Asynchronous exceptions are thrown by either a different user thread, or by the runtime system itself. For example, in the async package, race will kill the longer-running thread with an asynchronous exception. Similarly, the timeout function will kill an action which has run for too long. And the runtime system will kill threads which appear to be deadlocked on MVars or STM actions.

In contrast to synchronous exceptions, we almost never want to recover from asynchronous exceptions. In fact, this is a common mistake in Haskell code, and from what I've seen has been the largest source of confusion and concern amongst users when it comes to Haskell's runtime exception system.

Impure exceptions are hidden inside a pure value, and exposed by forcing evaluation of that value. Examples are error, undefined, and impureThrow. Additionally, incomplete pattern matches can generate impure exceptions. Ultimately, when these pure values are forced and the exception is exposed, it is thrown as a synchronous exception.

Since they are ultimately thrown as synchronous exceptions, when it comes to handling them, we want to treat them in all ways like synchronous exceptions. Based on the comments above, that means we want to be able to recover from impure exceptions.

...along with some general guidance...

All synchronous exceptions should be recoverable All asynchronous exceptions should not be recoverable In both cases, cleanup code needs to work reliably

Rescue is primarily focused on deligering a great experince when dealing with synchronous excpetions, though touches on async ones as well.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].