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

Proposal:Split MonadReader and MonadWriter #63

Closed
ekmett opened this issue Nov 19, 2015 · 5 comments · Fixed by #77
Closed

Proposal:Split MonadReader and MonadWriter #63

ekmett opened this issue Nov 19, 2015 · 5 comments · Fixed by #77

Comments

@ekmett
Copy link

ekmett commented Nov 19, 2015

In mtl I've long wanted to split apart MonadReader and MonadWriter into two parts each. Splitting off the algebraic effects in each case yields a subclass that is much more often implementable.

class MonadAsk e m | m -> e where
   ask :: m e

class MonadAsk e m => MonadReader e m | m -> e where
  local :: forall a. (e -> e) -> m a -> m a

class MonadTell w m | m -> w where
   tell :: w -> m ()

class MonadTell w m => MonadWriter w m | m -> w where
   listen :: forall a. m a -> m (Tuple a w)
   pass :: forall a. m (Tuple a (w -> w)) -> m a

Of course the fundeps there can't be written currently.

This has the benefit that a number of additional instances can exist for MonadAsk and MonadTell.

Notably you can use MonadTell for things like logging in some IO-like monad. Or with something like reflection you can make:

instance Refies s w => MonadAsk w (Tagged s) 
@jdegoes
Copy link

jdegoes commented Nov 19, 2015

👍

@paf31
Copy link
Contributor

paf31 commented Nov 19, 2015

👍

But I have a (slightly tangential) question about this actually. Recently I had a case where I needed to use MonadError and MonadWriter together, parametrically in the choice of monad. The laws I wanted actually didn't exist, because they relied on the combination of both instances (basically I wanted to say what happened to the log in the event of an error), and switching out the MonadWriter implementation for IO ended up breaking some code due to assumptions I'd made. Is there a good way of talking about laws between mtl classes? One option might be to add subclasses for each possible behavior, but then you end up with a quadratic blowup in the number of instances.

@ekmett
Copy link
Author

ekmett commented Nov 19, 2015

The monad transformer classes don't define the layering semantics. You can
think of MonadFoo as a constraint that asks for a handler to be installed
to handle this class of effects. You get this same problem with 'effect
handler' systems in general. You've built the product of two lawvere
theories, but not given the explicit layering.

One way to get an explicit layering is to work parametrically in whichever
effect has to be placed 'inside' with just some MonadFoo constraint and
then use an explicit EitherT or whatever wrapper on the outside. Most of
the time I need to do something like this with a known layering it is
largely local behavior and the outer wrapper can be removed afterwards.

On Thu, Nov 19, 2015 at 2:12 PM, Phil Freeman [email protected]
wrote:

[image: 👍]

But I have a (slightly tangential) question about this actually. Recently
I had a case where I needed to use MonadError and MonadWriter together,
parametrically in the choice of monad. The laws I wanted actually didn't
exist, because they relied on the combination of both instances (basically
I wanted to say what happened to the log in the event of an error), and
switching out the MonadWriter implementation for IO ended up breaking
some code due to assumptions I'd made. Is there a good way of talking about
laws between mtl classes? One option might be to add subclasses for each
possible behavior, but then you end up with a quadratic blowup in the
number of instances.


Reply to this email directly or view it on GitHub
#63 (comment)
.

@paf31
Copy link
Contributor

paf31 commented Nov 19, 2015

One way to get an explicit layering is to work parametrically in whichever effect has to be placed 'inside' with just some MonadFoo constraint and then use an explicit EitherT or whatever wrapper on the outside.

Right, I do this in the typechecker when I know I want to just put StateT on top of whatever base monad the user picked.

I suppose in the case where I really do want to be parametric in both, I could just define my own custom subclass and document its laws.

@ekmett
Copy link
Author

ekmett commented Nov 19, 2015

That is pretty much what I'd do.

On Thu, Nov 19, 2015 at 4:15 PM, Phil Freeman [email protected]
wrote:

One way to get an explicit layering is to work parametrically in whichever
effect has to be placed 'inside' with just some MonadFoo constraint and
then use an explicit EitherT or whatever wrapper on the outside.

Right, I do this in the typechecker when I know I want to just put StateT
on top of whatever base monad the user picked.

I suppose in the case where I really do want to be parametric in both, I
could just define my own custom subclass and document its laws.


Reply to this email directly or view it on GitHub
#63 (comment)
.

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 a pull request may close this issue.

3 participants