Say I have two type classes defined as follows that are identical in function but different in names:
class Monad m where
(>>=) :: m a -> (a -> m b) -> m b
return :: a -> m a
class PhantomMonad p where
pbind :: p a -> (a -> p b) -> p b
preturn :: a -> p a
Is there a way to tie these two classes together so something that is an instance of PhantomMonad will automatically be an instance of Monad, or will instances for each class have to be explicitly written? Any insight would be most appreciated, thanks!
Yes, but it requires the slightly alarming language extensions
FlexibleInstances
andUndecidableInstances
:FlexibleInstances
is not so bad, but the risk of undecidability is slightly more alarming. The issue is that in the inference rule, nothing is getting smaller, so if you combine this instance declaration with another similar one (like say the reverse direction), you could easily get the type checker to loop forever.I'm generally comfortable using
FlexibleInstances
, but I tend to avoidUndecidableInstances
without a very good reason. Here I agree with Don Stewart's suggestion that you'd be better off usingMonad
to begin with. But your question is more in the nature of a thought experiment, the answer is that you can do what you want without getting into an Oleg level of scariness.That's an unusual design. Can you not just remove the PhantomMonad, since it is isomorphic to the other class.
Though this doesn't really make sense, try
(maybe with some compiler warnings deactivated).
Another solution is to use
newtype
. This isn't exactly what you want, but is often used in such cases.This allows linking different ways of specifying the same structure. For example,
ArrowApply
(from Control.Arrow) andMonad
are equivalent. You can useKleisli
to make an ArrowApply out of a monad, andArrowMonad
to make a monad out of ArrowApply.Also, one-way wrappers are possible:
WrapMonad
(in Control.Applicative) forms an applicative out of a monad.Good answer: No, what you're hoping to do isn't really viable. You can write an instance that looks like it does what you want, possibly needing some GHC extensions in the process, but it won't work the way you you'd like it to.
Unwise answer: You can probably accomplish what you want using scary type-level metaprogramming, but it may get complicated. This really isn't recommended unless you absolutely need this to work for some reason.
Officially instances can't really depend on other instances, because GHC only looks at the "instance head" when making decisions, and class constraints are in the "context". To make something like a "type class synonym" here, you'd have to write what looks like an instance of
Monad
for all possible types, which obviously doesn't make sense. You'll be overlapping with other instances ofMonad
, which has its own problems.On top of all that, I don't think such an instance will satisfy the termination check requirements for instance resolution, so you'd also need the
UndecidableInstances
extension, which means the ability to write instances that will send GHC's type checker into an infinite loop.If you really want to go down that rabbit hole, browse around on Oleg Kiselyov's website a bit; he's sort of the patron saint of type-level metaprogramming in Haskell.
It's fun stuff, to be sure, but if you just want to write code and have it work, probably not worth the pain.
Edit: Okay, in hindsight I've overstated the issue here. Something like
PhantomMonad
works fine as a one-off and should do what you want, given theOverlapping
- andUndecidableInstances
GHC extensions. The complicated stuff starts up when you want to do anything much more complicated than what's in the question. My sincere thanks to Norman Ramsey for calling me on it--I really should have known better.I still don't really recommend doing this sort of thing without good reason, but it's not as bad as I made it sound. Mea culpa.