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

Derivable Algebra #359

Merged
merged 44 commits into from
Mar 3, 2020
Merged

Derivable Algebra #359

merged 44 commits into from
Mar 3, 2020

Conversation

robrix
Copy link
Contributor

@robrix robrix commented Mar 2, 2020

This PR changes Algebra’s alg method, giving it a parameter for a monad homomorphism which is applied to actions within the signature.

Some neat effects of this:

  • We can now derive Algebra instances using GeneralizedNewtypeDeriving or DerivingVia.
  • The homomorphism obviates the need for uses of hmap in the case of e.g. ReaderC.
  • The homomorphism obviates the need for uses of handleCoercible in the case of non-orthogonal carriers.
  • All together now: the homomorphism obviates the need for HFunctor altogether.

There’s a bunch of work to do:

  • Depends on Labelled effects #354 and a hackage release containing it.
  • Document the homomorphism parameter. Why is it a homomorphism? What does that mean? What should callers pass? (Why are you calling alg yourself, anyway?) What should instances do with it? Not going to bother with this in this PR since Distributive Algebra #361 replaces it anyway.
  • 🔥 handleCoercible.
  • 🔥 HFunctor and its generic derivation machinery.

robrix added 30 commits March 2, 2020 14:41
@robrix robrix mentioned this pull request Mar 3, 2020
5 tasks
@robrix robrix changed the base branch from master to label-maker March 3, 2020 11:53
@robrix robrix added this to the 1.1.0.0 milestone Mar 3, 2020
@robrix robrix changed the base branch from label-maker to master March 3, 2020 12:35
Comment on lines -58 to +63
class (HFunctor sig, Monad m) => Algebra sig m | m -> sig where
class Monad m => Algebra sig m | m -> sig where
-- | Construct a value in the carrier for an effect signature (typically a sum of a handled effect and any remaining effects).
alg :: sig m a -> m a
alg :: Monad n => (forall x . n x -> m x) -> sig n a -> m a
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 is the key change: sig now holds ns instead of ms, and a monad homomorphism is provided to map them into the carrier.

Comment on lines -82 to +85
send = alg . inj
send = alg id . inj
Copy link
Contributor Author

Choose a reason for hiding this comment

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

send always operates in the topmost algebra, so we start by just passing id.

Comment on lines -95 to +98
alg (Choose m) = m True S.<> m False
alg hom (Choose m) = hom (m True) S.<> hom (m False)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Terminal algebras only need to apply the homomorphism to any continuations or higher-order positions to produce an action in their carrier.

Comment on lines -121 to +131
alg (L (L (Throw e))) = Except.throwE e
alg (L (R (Catch m h k))) = Except.catchE m h >>= k
alg (R other) = Except.ExceptT $ alg (thread (Right ()) (either (pure . Left) Except.runExceptT) other)
alg hom = \case
L (L (Throw e)) -> Except.throwE e
L (R (Catch m h k)) -> Except.catchE (hom m) (hom . h) >>= hom . k
R other -> Except.ExceptT $ alg id (thread (Right ()) (either (pure . Left) (Except.runExceptT . hom)) other)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nonterminal algebras, on the other hand, also need to apply the homomorphism before threading through the rest of the signature.

Comment on lines -158 to +166
alg (L (Ask k)) = Reader.ask >>= k
alg (L (Local f m k)) = Reader.local f m >>= k
alg (R other) = Reader.ReaderT $ \ r -> alg (hmap (`Reader.runReaderT` r) other)
alg hom = \case
L (Ask k) -> Reader.ask >>= hom . k
L (Local f m k) -> Reader.local f (hom m) >>= hom . k
R other -> Reader.ReaderT $ \ r -> alg ((`Reader.runReaderT` r) . hom) other
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Algebras using hmap or handleCoercible can instead compose their handler onto the homomorphism passed to alg for the rest of the stack.

Comment on lines +97 to +98
dst :: ChooseC Identity (n a) -> m (ChooseC Identity a)
dst = runIdentity . runChoose (liftA2 (liftA2 (<|>))) (pure . runChoose (liftA2 (<|>)) (pure . pure) . hom)
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 use ScopedTypeVariables now since we close over hom. We could probably have removed the signature for dst instead, but the signatures for these are a bit subtle since they use the same carrier type but containing Identity, so I wanted to leave the signatures intact.

Comment on lines -53 to +54
newtype CullC m a = CullC (ReaderC Bool (NonDetC m) a)
newtype CullC m a = CullC { runCullC :: ReaderC Bool (NonDetC m) a }
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 introduced selectors for a few types like this, but I don’t export them. I could maybe have used coerce . hom in the algebra instead of runCullC . hom, but it should be eliminated either way, and this way is clearer.

deriving (Applicative, Functor, Monad, Fail.MonadFail, MonadFix, MonadIO, MonadTrans)
deriving (Algebra (Error e :+: sig), Applicative, Functor, Monad, Fail.MonadFail, MonadFix, MonadIO, 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.

This is one of the only cases where we define a carrier wrapping another one just so that we can customize some instance other than Algebra, and so is likewise one of a small number of cases where we can derive an Algebra instance.

Comment on lines -40 to +41
{ runHandler :: forall s x . sig (InterpretC s sig m) x -> InterpretC s sig m x }
{ runHandler :: forall s n x . Monad n => (forall y . n y -> InterpretC s sig m y) -> sig n x -> InterpretC s sig m x }
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 has to take a homomorphism just like alg, or else we have to keep HFunctor around. I think this is reasonable: the whole idea of runInterpret is that it allows you to write “a real algebra” inline.

Comment on lines -44 to -45
handleCoercible :: (HFunctor sig, Functor f, Coercible f g) => sig f a -> sig g a
handleCoercible = hmap 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.

All uses of hmap in the library are subsumed by the homomorphism parameter to alg, so we can eliminate handleCoercible along with the rest.

@robrix robrix marked this pull request as ready for review March 3, 2020 16:37
@robrix robrix merged commit 6d4b463 into master Mar 3, 2020
@joshvera joshvera deleted the derivable-algebra branch March 3, 2020 17:56
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