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

Separate carrier and effect modules #204

Merged
merged 113 commits into from
Sep 23, 2019
Merged

Conversation

robrix
Copy link
Contributor

@robrix robrix commented Sep 1, 2019

This PR separates carriers and effects into disjoint hierarchies. Goals include:

  • More clearly delineating interfaces and implementations: interfaces go in Control.Effect.*, implementations in Control.Carrier.*.
  • Easier experimentation with new carriers: there’s an easy and obvious place to put new carrier modules; carriers could potentially be defined for monads from base & transformers. (cf Define (terminal) Carrier instances for common monads? #183) I intend to explore this in separate PRs.
  • Consistent API between carriers. We could potentially even abstract carriers via typeclasses or Backpack signatures. Even absent signatures, sharing the same interface between multiple carriers also implies that swapping one for another can be as simple as switching which module you import.
  • Making the choice of which carrier to use conscious & explicit: “I imported strict StateC” instead of “whichever one the effect module re-exports.”
  • Retaining good import ergonomics: carrier modules re-export the effect & Carrier machinery.
  • Documenting the laws for each effect constructor, and then verifying them separately against each carrier (to be fully realized in a follow-up PR). (cf Effect laws are undocumented #142)

On the other hand, this is also quite a disruptive PR, since it means pretty significant changes for consumers of the library. I’d therefore like to get input from users on this!

It also builds on several other PRs currently slated for 0.6:

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.

@@ -0,0 +1,11 @@
{-# LANGUAGE FunctionalDependencies #-}
module Control.Carrier.Class
( Carrier(..)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Instead of exporting run, Member, &c. from this module, we export them from Control.Carrier. Likewise, send has been moved into Control.Effect.Sum.

@@ -0,0 +1,90 @@
{-# LANGUAGE DeriveTraversable, FlexibleInstances, MultiParamTypeClasses, RankNTypes, TypeOperators, UndecidableInstances #-}
module Control.Carrier.Choose.Church
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Carrier modules are now named descriptively of the implementation; .Church here indicates that this is a church-encoded carrier for Choose.

@@ -0,0 +1,77 @@
{-# LANGUAGE DeriveFunctor, FlexibleInstances, MultiParamTypeClasses, TypeOperators, UndecidableInstances #-}
module Control.Carrier.Empty.Maybe
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Likewise with direct-stye carriers (i.e. those returning m (f a) for some functor f), we name the module after the functor being returned.

@@ -0,0 +1,53 @@
{-# LANGUAGE FlexibleInstances, GeneralizedNewtypeDeriving, MultiParamTypeClasses, TypeOperators, UndecidableInstances #-}
module Control.Carrier.Fresh.Strict
Copy link
Contributor Author

Choose a reason for hiding this comment

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

With stateful carriers, we instead note .Lazy or .Strict. (We only provide .Strict for the Fresh and Writer effects, but State offers both varieties.)

import Control.Effect.Carrier
import Control.Effect.State
import Control.Carrier
import Control.Carrier.State.Strict
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This illustrates the benefit of the new hierarchy nicely: the properties of the selected carrier are described by the module being imported.


instance (Carrier sig m, Member Choose sig) => S.Semigroup (Choosing m a) where
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Kinda snuck this in here, but it’s extremely convenient to have available. We’re using an explicit qualified Data.Semigroup as S import here to accommodate ghc 8.2.

-- | Conditional failure, returning only if the condition is 'True'.
guard :: (Carrier sig m, Member Empty sig) => Bool -> m ()
guard True = pure ()
guard False = empty
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Snuck this in as well, with essentially the same rationale as above.

instance MonadPlus (NonDetC m)
-- | Map a 'Foldable' collection of values into a nondeterministic computation using the supplied action.
foldMapA :: (Foldable t, Alternative m) => (a -> m b) -> t a -> m b
foldMapA f = getAlt #. foldMap (Alt #. f)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Snuck this in too. I end up defining this in just about every project that I use Alternative in; I’m considering proposing its addition to base (tho prolly under a different name, maybe asumMap because concat : concatMap :: asum : asumMap).

--
-- cf https://github.com/fused-effects/diffused-effects/pull/1#discussion_r323560758
(#.) :: Coercible b c => (b -> c) -> (a -> b) -> (a -> c)
(#.) _ = coerce
Copy link
Contributor Author

Choose a reason for hiding this comment

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

🎩 @patrickt for pointing this out

-- | Construct a request for an effect to be interpreted by some handler later on.
send :: (Member effect sig, Carrier sig m) => effect m a -> m a
send = eff . inj
{-# INLINE send #-}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Glad to have this, and the HFunctor/Effect instances, back where they belong.

@robrix
Copy link
Contributor Author

robrix commented Sep 23, 2019

🚀

@robrix robrix merged commit 4e34881 into master Sep 23, 2019
@robrix robrix deleted the separate-carrier-and-effect-modules branch September 23, 2019 02:20
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.

None yet

1 participant