lexi-lambda / freer-simple

A friendly effect system for Haskell
https://hackage.haskell.org/package/freer-simple
BSD 3-Clause "New" or "Revised" License
227 stars 19 forks source link

Provide instances for MTL interop #18

Open andremarianiello opened 5 years ago

andremarianiello commented 5 years ago

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.

isovector commented 5 years ago

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

andremarianiello commented 5 years ago

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

ocharles commented 5 years ago

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

?

andremarianiello commented 5 years ago

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

ocharles commented 5 years ago

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

andremarianiello commented 5 years ago

@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.

ocharles commented 5 years ago

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:

andremarianiello commented 5 years ago

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.

ocharles commented 5 years ago

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).

andremarianiello commented 5 years ago

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.

isovector commented 5 years ago

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

@ocharles' fails the fundep coverage, even with UndecidableInstances

andremarianiello commented 5 years ago

@isovector Thanks for checking that out.

ocharles commented 5 years ago

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.

xaviershay commented 5 years ago

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.)