freer-simple icon indicating copy to clipboard operation
freer-simple copied to clipboard

Provide instances for MTL interop

Open andremarianiello opened this issue 6 years ago • 14 comments

Some libraries, like lens, provide functions that are meant for use in MTL transformer stacks. Example:

http://hackage.haskell.org/package/lens-4.17/docs/Control-Lens-Setter.html#g:5

If Eff had an instance for MonadState, then I could use those Lens functions without having to rewrite them using the put and get from Control.Monad.Freer.State - it would happen automatically.

I have spent a number of days on this, but my instance manipulation skills are not the greatest, so I ended up with something like this:

instance MTL.MonadState s (Eff (State s ': effs)) where
get = get
put = put

instance {-# OVERLAPPABLE #-} MTL.MonadState s (Eff effs) => MTL.MonadState s (Eff (eff ': effs)) where
get = MTL.get
put = MTL.put

-- single actions that uses MTL and Eff values together without wrapping/unwrapping newtypes
action :: Eff (State Bool ': effs) ()
action = do
b <- MTL.get
put $ not b

ranAction :: Eff effs ((), Bool)
ranAction = runState True action

I also tried a solution involves defining an instance for a newtype wrapping Eff and using the constraints library to unsafely deduce MonadState s (Eff effs) from Member (State s) effs, but I had to use explicit TypeApplication to get it to work. I can post the code for that if you think that avenue is more likely where you would want to go with this.

A third possibility would be using something from the reflection package to provide the instance at runtime, but I haven't explored that.

I have only been playing around with

TL;DR: I don't know the best way to do this, but I think this library can provide something helpful for this use case.

andremarianiello avatar Dec 03 '18 01:12 andremarianiello

Does this work? I'd expect the fundeps on MonadState to get in the way.

isovector avatar Feb 12 '19 20:02 isovector

Been a while since I did this, but I believe the example I posted worked as written.

andremarianiello avatar Feb 12 '19 21:02 andremarianiello

These don't look quite right to me. Can't you just write

instance Member (State s) effs => MonadState s (Eff effs) where
  get = get -- from freer-simple

?

ocharles avatar Feb 15 '19 12:02 ocharles

@ocharles That was my initial attempt, but I don't think it worked because Eff effs does not have s in it anywhere.

andremarianiello avatar Feb 15 '19 13:02 andremarianiello

Hm, I think it should be fine, will just probably need -XUndecidableInstances

ocharles avatar Feb 15 '19 13:02 ocharles

@ocharles That is possible. I wasn't sure whether UndecidableInstances was morally correct in this case. If it is, and it works, then I much prefer your instance.

andremarianiello avatar Feb 15 '19 14:02 andremarianiello

I think it should be fine. All UndecidableInstances does is lift GHC's instance resolution termination checker, which is intentionally very simplistic. It just moves the burden of proof that instance resolution will terminate to you. In this case, we know Member (State s) effs will terminate, so we're good :+1:

ocharles avatar Feb 15 '19 14:02 ocharles

Do we know Member (State s) effs will terminate because it doesn't require MonadState s (Eff effs), thus we know it won't loop forever? I don't know anything about proving that the checker terminates.

andremarianiello avatar Feb 15 '19 14:02 andremarianiello

Member (State s) effs shouldn't have any interaction with MonadState. All Member does is recurse down the effs list looking for the given key (as we can see by looking at the equations for FindElem).

ocharles avatar Feb 15 '19 14:02 ocharles

Hmm, I'm thinking back now, and I bet I probably tried adding UndecidableInstances to see if it would work, but I still got another error when I tried it. I'll have to go back and try it again. I'll post the results here when I do.

andremarianiello avatar Feb 15 '19 14:02 andremarianiello

The originally given instance works with UndecidableInstances, and complains about the fundep without.

@ocharles' fails the fundep coverage, even with UndecidableInstances

isovector avatar Feb 15 '19 14:02 isovector

@isovector Thanks for checking that out.

andremarianiello avatar Feb 15 '19 15:02 andremarianiello

Oh right, yes, Member doesn't have a fun dep so of course it wouldn't work. Grumbles something about #14. The given instances above look reasonable then.

ocharles avatar Feb 15 '19 17:02 ocharles

I wrote these freer wrappers for a couple of lens functions if useful to anyone:

import Control.Monad.Freer
import Control.Monad.Freer.State
import qualified Control.Lens

assign :: Members '[ State s ] effs => Control.Lens.ASetter s s a b -> b -> Eff effs ()
assign accessor value = get >>= put . Control.Lens.set accessor value

modifying :: Members '[ State s ] effs => Control.Lens.ASetter s s a b -> (a -> b) -> Eff effs ()
modifying accessor f = get >>= put . Control.Lens.over accessor f

(I know that's not the overall point of this ticket, but the above is what I was looking for when I ended up here.)

xaviershay avatar Feb 16 '19 02:02 xaviershay