-
Notifications
You must be signed in to change notification settings - Fork 53
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Split Error into Throw & Catch effects #247
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ready for review.
-- | @m@ is a carrier for @sig@ containing @eff@. | ||
-- | ||
-- Note that if @eff@ is a sum, it will be decomposed into multiple 'Member' constraints. While this technically allows one to combine multiple unrelated effects into a single 'Has' constraint, doing so has two significant drawbacks: | ||
-- | ||
-- 1. Due to [a problem with recursive type families](https://gitlab.haskell.org/ghc/ghc/issues/8095), this can lead to significantly slower compiles. | ||
-- | ||
-- 2. It defeats @ghc@’s warnings for redundant constraints, and thus can lead to a proliferation of redundant constraints as code is changed. | ||
type Has eff sig m = (Members eff sig, Carrier sig m) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We’ve had a rather nasty gotcha lurking since #219 that wasn’t discovered until now: Using a Has
constraint with an effect defined as a sum wouldn’t allow ghc
to solve Member
constraints for the members of that sum. Basically, given Member (A :+: B) sig
, ghc
wasn’t able to deduce Member A sig
, which is necessary for Has A sig m
to be solved.
This remained latent because NonDet
, the only other effect defined as a sum of other effects, is used exclusively via the Alternative
interface—we never use Has NonDet sig m
constraints. It surfaced now because the common pattern is to use Error e
even when you only need Throw e
—in no small part because it wasn’t possible to say solely Throw e
until this PR.
The solution is to decompose the sum into a series of individual Member
constraints. Note that this technically makes Has
capable of expressing multiple unrelated effects in a single constraint (synonym):
foo :: Has (Reader r :+: State i :+: Error e :+: Writer w) sig m => m ()
However, as documented here and on Members
, this can cause performance problems as well as defeating warnings, so it’s best to avoid it.
src/Control/Effect/Catch.hs
Outdated
-- | Run a computation which can throw errors with a handler to run on error. | ||
-- | ||
-- Errors thrown by the handler will escape up to the nearest enclosing 'catchError' (if any). | ||
-- Note that this effect does /not/ handle errors thrown from impure contexts such as IO, | ||
-- nor will it handle exceptions thrown from pure code. If you need to handle IO-based errors, | ||
-- consider if 'Control.Effect.Resource' fits your use case; if not, use 'liftIO' with | ||
-- 'Control.Exception.try' or use 'Control.Exception.Catch' from outside the effect invocation. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I’ve removed the property tests for the time being; they’re being moved in #239 anyway.
|
||
instance HFunctor Fail | ||
instance Effect Fail | ||
type Fail = Throw String |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is no longer any reason for Fail
to be distinct from Throw
!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
😍
newtype ThrowC e m a = ThrowC { runThrowC :: ErrorC e m a } | ||
deriving (Alternative, Applicative, Functor, Monad, Fail.MonadFail, MonadFix, MonadIO, MonadPlus, MonadTrans) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can, of course, also just use ErrorC
directly, but maybe you want to guarantee the absence of Catch
from the signature or something.
I am really quite happy with how this one turned out ✨ |
|
||
instance HFunctor Fail | ||
instance Effect Fail | ||
type Fail = Throw String |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
😍
-- | Decompose sums on the left into multiple 'Member' constraints. | ||
-- | ||
-- Note that while this, and by extension 'Control.Carrier.Has', can be used to group together multiple membership checks into a single (composite) constraint, large signatures on the left can slow compiles down due to [a problem with recursive type families](https://gitlab.haskell.org/ghc/ghc/issues/8095). | ||
type family Members sub sup :: Constraint where |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for this explicit kind signature 👍🏻
This PR explores splitting
Error
up into separateThrow
andCatch
effects.Error e
as a synonym forThrow e :+: Catch e
.Fail
as a synonym forThrow String
.Fail
effect? #154.