purescript/purescript-transformers

Proposal:Split MonadReader and MonadWriter

ekmett opened this issue ยท 5 comments

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) 

๐Ÿ‘

๐Ÿ‘

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.

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 notifications@github.com
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)
.

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.

That is pretty much what I'd do.

On Thu, Nov 19, 2015 at 4:15 PM, Phil Freeman notifications@github.com
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)
.