views:

180

answers:

5

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 b

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!

+3  A: 

That's an unusual design. Can you not just remove the PhantomMonad, since it is isomorphic to the other class.

Don Stewart
You're right, the design isn't viable (and is really kind of pointless). I decided to avoid doing this, but was still curious if Haskell had some kind of facilities to do this and I was missing them. It probably doesn't, because this is something sane people wouldn't do.
thegravian
@Don Stewart: similar cases would be `mtl`'s and `transformers`'s `MonadTrans` class. they're exactly the same. an answer on how to unify classes (had it been possible) in this simple esoteric example could had also been tremendously useful for real code.
yairchu
@yairchu: To be fair, Don's solution of "remove one class" is the correct solution to that case as well, namely removing `mtl`...
camccann
@camccann: It would still be useful to use different monad transformers together right now. And a great thing about Haskell's type-classes is that they are open (unlike Java's interfaces where you can't add an instance to `int`). Being able to merge and tweak type-classes would make them more open. Let's say for example that someone implemented a "Monad" class and he didn't make it extend `Functor`, as other people found that he probably should had; the ability to tweak/merge the class post-mortem would had been very useful then.
yairchu
A: 

Though this doesn't really make sense, try

instance Monad m => PhantomMonad m where
    pbind = (>>=)
    preturn = return

(maybe with some compiler warnings deactivated).

Dario
That's the other way around from what he asked for, though. Of course, `instance (PhantomMonad m) => Monad m where ...` will cause even more problems.
camccann
+7  A: 

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 of Monad, 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 the Overlapping- and UndecidableInstances 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.

camccann
Thanks! I was curious if I was just missing something obvious with my convoluted thinking or if this was really that screwed up. Needless to say, I'm sticking with the "Good" answer.
thegravian
@thegravian: A wise decision. If it helps, your idea isn't inherently absurd, it just isn't workable given Haskell's type class system as it stands. I think there have been some proposed extensions that would make it work, but none have been implemented so far.
camccann
@camcann: The solution isn't really all *that* scary, is it? I mean, the word "undecidable" is a little scary, but Haskell type checking is already complete for exponential time, so I wouldn't let it stop me from doing something I really wanted to do...
Norman Ramsey
@Norman Ramsey: The resulting type-level program is the painful part, `UndecidableInstances` is just the icing on the cake. I mention it partly *because* it sounds intimidating--having done some non-trivial type-level programming, it's actually turning on `OverlappingInstances` that scares *me* these days, heh. As far as type metaprogramming goes this question isn't *that* bad, but it's still not something I'm going to recommend. All the necessary information is in Oleg's articles; those who seek, shall find.
camccann
@camccann: I can't find the pain. It just looks like an ordinary instance declaration to me. Prolog in the type checker as usual. What am I missing?
Norman Ramsey
@Norman Ramsey: You know, you're right--without MPTCs or multiple overly-generic instances, it's not as bad as I was thinking. Not particularly extensible, but not horrible.
camccann
@Norman Ramsey: I've edited my answer to recant somewhat. Clearly I've spent too much time misusing GHC's type system, and let my paranoia get the better of me.
camccann
@camccann: Paranoia is good. Recently we've been letting the type system abuse us---we have a program on which GHC fails to terminate...
Norman Ramsey
@Norman Ramsey: That's a fairly uninteresting fact unless you mean "...without `UndecidableInstances` enabled" in which case I'm both frightened and curious. Got a link to details anywhere? I've got about six lines of (I think valid Haskell 98) code that causes GHC to peg 100% CPU and eat dozens of GB of memory until I kill the process, but that's merely *practical* non-termination and nowhere near as interesting.
camccann
@camccann: My postdoc is trying to get a fold over a GADT to typecheck. I'm certain-sure that we don't have `UndecidableInstances` on anywhere. If you want an example of the code send me an email to [email protected] and we'll pass it along...
Norman Ramsey
+3  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?

Yes, but it requires the slightly alarming language extensions FlexibleInstances and UndecidableInstances:

instance (PhantomMonad m) => Monad m where
  return = preturn
  (>>=)  = pbind

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 avoid UndecidableInstances without a very good reason. Here I agree with Don Stewart's suggestion that you'd be better off using Monad 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.

Norman Ramsey
This doesn't work as is because you've overlapped every other instance of `Monad`. But after playing around trying to break stuff, it seems that GHC is smarter than I thought with single-parameter type classes. Obviously you only get one generic instance like this, but as a one-off, this works fine given `OverlappingInstances`.
camccann
+2  A: 

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) and Monad are equivalent. You can use Kleisli to make an ArrowApply out of a monad, and ArrowMonad to make a monad out of ArrowApply.

Also, one-way wrappers are possible: WrapMonad (in Control.Applicative) forms an applicative out of a monad.

class PhantomMonad p where
    pbind    :: p a -> (a -> p b) -> p b
    preturn  :: a -> p a

newtype WrapPhantom m a = WrapPhantom { unWrapPhantom :: m a }

newtype WrapReal m a = WrapReal { unWrapReal :: m a }

instance Monad m => PhantomMonad (WrapPhantom m) where
  pbind (WrapPhantom x) f = WrapPhantom (x >>= (unWrapPhantom . f))
  preturn = WrapPhantom . return

instance PhantomMonad m => Monad (WrapReal m) where
  WrapReal x >>= f = WrapReal (x `pbind` (unWrapReal . f))
  return = WrapReal . preturn
sdcvvc
@sdcvvc: Couldn't find a `WrapMonad` in `Control.Arrow` but `instance ArrowApply a => Monad (a ())`. `newtype Kleisli m a b = Kleisli (a -> m b)`;`instance Monad m => ArrowApply (Kleisli m)`; Do a round-trip through these instances and you don't end up back at the same `ArrowApply` you started with. `pre a x y = a x y`, `post a x y = x -> a () y`.
yairchu
Whoops, I meant ArrowMonad.
sdcvvc