dubiousjim
dubiousjim

Reputation: 4822

Can one compose types in a Haskell instance declaration?

I've written a Haskell typeclass and it would be convenient to declare instances of it using types of the form (a -> m _), where m is of kind (* -> *), such as a monad, and _ is a slot to be left unsaturated. I know how to write newtype X a m b = X (a -> m b), and declaring an instance for X a m. But what I'm looking for is to instead use the bare, unwrapped -> type, if that's possible.

If one wants to declare instances for types of the form (a -> _), then you can just write:

instance Foo a ((->) a) where ...

but I don't know how/whether one can do it with types of the form (a -> m _). I guess I'm looking to compose the type constructor (->) a _ and the type constructor m _ in my instance declaration.

I'd like to write something like this:

instance Foo a ((->) a (m :: *->*)) where ...

or:

instance Foo a ((->) a (m *)) where ...

but of course these don't work. Is it possible to do this?

Concretely, here's what I'm trying to achieve. I wrote a typeclass for MonadReaders that are embedded inside (one level) of other MonadReaders, like this:

{-# LANGUAGE FunctionalDependencies FlexibleInstances
UndecidableInstances  #-}

class MonadReader w m => DeepMonadReader w r m | m -> r where
  { deepask   :: m r
  ; deepask = deepreader id
  ; deeplocal :: (r -> r) -> m a -> m a
  ; deepreader :: (r -> a) -> m a
  ; deepreader f = do { r <- deepask; return (f r) }
  }

instance MonadReader r m => DeepMonadReader w r (ReaderT w m) where
  { deepask = lift ask
  ; deeplocal = mapReaderT . local
  ; deepreader = lift . reader
  }

It'd be nice to also provide an instance something like this:

instance MonadReader r m => DeepMonadReader w r ((->) w (m :: * ->
*)) where
  { deepask = \w -> ask
  ; deeplocal f xx = \w -> local f (xx w)
  ; deepreader xx = \w -> reader xx
  }

Upvotes: 1

Views: 271

Answers (1)

ErikR
ErikR

Reputation: 52039

I think you're on the wrong track and are making things a lot more complicated than they need to be.

Some observations:

... ((->) w (m :: * -> *)) ...

Let's explore what you mean by this. You are using it for the type parameter m in your DeepMonadReader class, and therefore it needs to be a monad. Can you give a concrete example of a monad which has this type? Why not just use ((->) w) ?

class MonadReader w m => DeepMonadReader w r m | m -> r where ...

The fact that w never apears in any member signatures is an indication something is amiss.

... I wrote a typeclass for MonadReaders that are embedded inside (one level) of other MonadReaders ...

I would take the reverse perspective. It makes sense to talk of monad stacks which are a transformed version of another monad stack. E.g.:

StateT s (WriterT w IO)   "contains"     IO
WriterT w (Maybe a)       "contains"     Maybe a

And what does it mean for a monad stack m1 to "contain" another monad m2? It just means that there is a way to convert computations in m2 to computations in m1:

convert ::  m2 a -> m1 a

Of course, this is just lift when using monad transformers.

To express your concept of a monad reader embedded in another monad, I would use this type class:

class HasReader m m' r where ...
  deepAsk :: m r
  deepLocal :: (r -> r) -> m' a -> m a

The idea here is that an instance HasReader m m' r expresses the fact that monad m "contains" a monad m' which itself is a reader with environment r.

deepAsk returns the environment of m' but as a computation in m.

deepLocal runs a computation in m' with a environment modification function but returns it as a computation in m. Note how this type signature is different from yours: my deepLocal uses different monads, m' and m whereas yours just goes from m to m.

The next step is decide which triples (m, m', r) do we want to write instances of HasReader for. Clearly it seems you had instances like this in mind:

m                                    m'                           r
---------------------                -----------                  --
ReaderT s (ReaderT r m)              ReaderT r m                  r
ReaderT t (ReaderT s (ReaderT r m)   ReaderT s (Reader T r m)     s
...

but it also seems reasonable to want to have these instances:

StateT s (ReaderT r m)               ReaderT r m                  r
WriterT w (ReaderT r m)              ReaderT r m                  r
MaybeT (ReaderT r m)                 ReaderT r m                  r
...

It turns out, though, that we don't need the HasReader class for any of these cases. We can just write the expression as a computation in m' and lift it up to m.

Upvotes: 2

Related Questions