3

I have the type

ActionT TL.Text (ReaderT T.Text IO)

I'm trying to make a MonadReader instance for this so that I do not have to lift ask, but always get

 (All instance types must be of the form (T a1 ... an)
  where a1 ... an are *distinct type variables*,
  and each type variable appears at most once in the instance head.
  Use -XFlexibleInstances if you want to disable this)

I've tried a bunch of instance types, a couple below, but they always get the above error

instance MonadReader T.Text (ActionT TL.Text (ReaderT T.Text IO))    

instance MonadReader r (ActionT TL.Text (ReaderT r IO))

instance (ScottyError e, MonadReader r m) => MonadReader r (ActionT e (ReaderT r m))

I feel like I'm missing something fundamental with instances. I think I understand FlexibleInstances but I can't see how that applies here.

Any help with the instance type would be appreciated, I would like to implement ask and local myself as the main goal is learning.

Thanks.

Update

I got it working with FlexibleInstances, MultiParamTypeClasses, UndecidableInstances and

instance  (ScottyError e, Monad m, MonadReader r m) => MonadReader r (ActionT e m) where
  ask = lift ask

Still working on the implementation of local. I also assume UndecidableInstances is bad?

Update 2

I think what I really need is.

instance  (ScottyError e, Monad m, MonadReader r m) => MonadReader r (ActionT e (ReaderT r m)) where

But I still cant figure out local

Ivan Meredith
  • 2,222
  • 14
  • 13
  • I'd suggest to move your answer from the question part into a proper answer. It's perfectly fine to answer your own question, it's even [encouraged](https://stackoverflow.com/help/self-answer). – Petr Apr 18 '14 at 14:55
  • 1
    `UndecidableInstances` isn't really problematic anyway. It allows you to define instances that might theoretically loop the type checker, except the type checker has a built-in recursion limit to make that fail at compile time. It's `OverlappingInstances` and `IncoherentInstances` that you want to enable only if you know exactly how they work and why they're not part of the standard. `UndecidableInstances` is harmless in comparison. – Carl Apr 18 '14 at 16:11
  • Thanks for the explanation, this is all very new to me. – Ivan Meredith Apr 19 '14 at 01:11

2 Answers2

1

As you answered yourself, you need UndecidableInstances to implement the fully generic MonadReader instance. This is a necessary evil, you can see it in all libraries that implement such generic monad type class instances.

I'm afraid that implementing local is going to be a problem. If you look at the standard instances, they all use some kind of mapping function that is specific for a particular monad. And since it seems that neither ActiveT exports such a method or its internals, it doesn't look feasible.

Petr
  • 62,528
  • 13
  • 153
  • 317
  • Thanks, I came to this conclusion too. I have forked the repository and was going to add a MonadReader instances for ActionT. However I also noted the map<..>T functions and I wonder if implementing that would be better, although there is also nothing stopping me from doing both. – Ivan Meredith Apr 19 '14 at 01:04
  • @IvanMeredith I'd be in favor of implementing something like `mapActionT :: (m a -> n a) -> ActionT m a -> ActionT n a`. Since `ActionT` is internally just a composition of monad transformers, this should be easy, just compose the corresponding `map..` functions. And this can be reused for many other purposes - there are many cases where it's convenient to change the inner monad. For example `mapActionT liftIO :: (MonadIO m) => ActionT IO a -> ActionT m a`. And it also allows you to implement the `MonadReader` instance very easily. – Petr Apr 19 '14 at 06:00
  • @IvanMeredith Moreover, `mapActionT` allows to easily implement [`hoist`](http://hackage.haskell.org/package/mmorph-1.0.0/docs/Control-Monad-Morph.html#t:MFunctor) from the _mmorph_ package (just as `hoist f = mapActionT f`), which provides a very generic interface for working with [monad morphisms](http://www.haskellforall.com/2013/03/mmorph-100-monad-morphisms.html). – Petr Apr 19 '14 at 06:05
  • @PatrPudlak Right, I had come up with that type signature, but was stuggling to implement the `(m a -> n a) -> StateT -> StateT` part of the stack with mapStateT. however I will look into hoist. I've seen it before in scalaz, and I thought `m a -> n a` was familiar but didn't remmeber where from. – Ivan Meredith Apr 20 '14 at 08:51
1

The instance would be

instance  (ScottyError e, Monad m, MonadReader r m) => MonadReader r (ActionT e (ReaderT r m)) where

However Scotty does not expose the required functions to implement local.

I am attempting to write a mapActionT to make it possible to implement local and I will update this answer if I ever figure out the types to do that :)

Edit:

I think I have mapActionT, but I'm not sure.

mapActionT :: (Monad m, Functor n, ScottyError e) => (forall a . m a -> n a) -> ActionT e m b -> ActionT e n b
mapActionT nat m = ActionT $ flip mapErrorT (runAM m) $ \rt -> 
        flip mapReaderT rt $ \st ->
            StateT $ \s -> fmap (\a -> (a,s)) (nat (evalStateT st s))

I ended up copying the type signature from hoist so that I could bring in Functor n for my fmap.

Any comments on the implementation are welcome.

Ivan Meredith
  • 2,222
  • 14
  • 13