freer-simple
freer-simple copied to clipboard
Provide instances for MTL interop
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.
Does this work? I'd expect the fundeps on MonadState
to get in the way.
Been a while since I did this, but I believe the example I posted worked as written.
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 That was my initial attempt, but I don't think it worked because Eff effs
does not have s
in it anywhere.
Hm, I think it should be fine, will just probably need -XUndecidableInstances
@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.
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:
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.
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
).
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.
The originally given instance works with UndecidableInstances
, and complains about the fundep without.
@ocharles' fails the fundep coverage, even with UndecidableInstances
@isovector Thanks for checking that out.
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.
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.)