Skip to content
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

Merged
merged 74 commits into from
Oct 11, 2019
Merged

Split Error into Throw & Catch effects #247

merged 74 commits into from
Oct 11, 2019

Conversation

robrix
Copy link
Contributor

@robrix robrix commented Oct 4, 2019

This PR explores splitting Error up into separate Throw and Catch effects.

Copy link
Contributor Author

@robrix robrix left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ready for review.

Comment on lines +18 to +25
-- | @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)
Copy link
Contributor Author

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.

Comment on lines 22 to 28
-- | 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.
Copy link
Contributor Author

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
Copy link
Contributor Author

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!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😍

Comment on lines +27 to +28
newtype ThrowC e m a = ThrowC { runThrowC :: ErrorC e m a }
deriving (Alternative, Applicative, Functor, Monad, Fail.MonadFail, MonadFix, MonadIO, MonadPlus, MonadTrans)
Copy link
Contributor Author

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.

@robrix robrix marked this pull request as ready for review October 9, 2019 03:35
@robrix
Copy link
Contributor Author

robrix commented Oct 9, 2019

I am really quite happy with how this one turned out ✨

@robrix robrix mentioned this pull request Oct 9, 2019
5 tasks

instance HFunctor Fail
instance Effect Fail
type Fail = Throw String
Copy link
Collaborator

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
Copy link
Collaborator

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 👍🏻

@robrix robrix merged commit 51860e8 into master Oct 11, 2019
@robrix robrix deleted the splitsville branch October 11, 2019 18:53
@robrix robrix mentioned this pull request Oct 11, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Polymorphic Fail effect?
2 participants