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 amb = X (a -> mb)
, and declaring an instance for X am
. 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
}
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 wm => DeepMonadReader wrm | 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. Eg:
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 mm' 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 .