views:

479

answers:

2

Consider the following example program:

next :: Int -> Int
next i
  | 0 == m2 = d2
  | otherwise = 3 * i + 1
  where
    (d2, m2) = i `divMod` 2

loopIteration :: MaybeT (StateT Int IO) ()
loopIteration = do
  i <- get
  guard $ i > 1
  liftIO $ print i
  modify next

main :: IO ()
main = do
  (`runStateT` 31) . runMaybeT . forever $ loopIteration
  return ()

It can only use get instead of lift get because instance MonadState s m => MonadState s (MaybeT m) is defined in the MaybeT module.

Many such instances are defined in kind of a combinatoric explosion manner.

It would have been nice (although impossible? why?) if we had the following type-class:

{-# LANGUAGE MultiParamTypeClasses #-}

class SuperMonad m s where
  lifts :: m a -> s a

Let's try to define it as such:

{-# LANGUAGE FlexibleInstances, ... #-}

instance SuperMonad a a where
  lifts = id

instance (SuperMonad a b, MonadTrans t, Monad b) => SuperMonad a (t b) where
  lifts = lift . lifts

Using lifts $ print i instead of liftIO $ print i works, which is nice.

But using lifts (get :: StateT Int IO Int) instead of (get :: MaybeT (StateT Int IO) Int) doesn't work.

GHC (6.10.3) gives the following error:

Overlapping instances for SuperMonad
                            (StateT Int IO) (StateT Int IO)
  arising from a use of `lifts'
Matching instances:
  instance SuperMonad a a
  instance (SuperMonad a b, MonadTrans t, Monad b) =>
           SuperMonad a (t b)
In a stmt of a 'do' expression:
    i <- lifts (get :: StateT Int IO Int)

I can see why "instance SuperMonad a a" applies. But why does GHC think that the other one does, too?

+4  A: 

Just because you haven't defined an instance in your current module doesn't mean that one couldn't be defined somewhere else.

{-# LANGUAGE ... #-}
module SomeOtherModule where

-- no practical implementation, but the instance could still be declared
instance SuperMonad (StateT s m) m

Suppose your module and SomeOtherModule are linked together in a single program.

Now, answer this: does your code use

instance SuperMonad a a
  -- with a = StateT Int IO

or

instance (SuperMonad a b, MonadTrans t, Monad b) => SuperMonad a (t b)
  -- with a = StateT Int IO
  --      t = StateT Int
  --      b = IO

?

ephemient
thanks. but I'm still confused: on their own, my instances don't overlap, but someone can define an instance that would make my instances overlap. can't someone always define an instance to overlap with my instance?
yairchu
Your structure makes it impossible for the compiler to unambiguously determine which instance to be used in your code. If it could be unambiguously resolved, then an overlap elsewhere wouldn't matter.
ephemient
@ephemient: that is because of the specific way the compiler works, right? I say that since I can unambiguously determine which instance can be used.
yairchu
No, this is just one of those dangers that is exposed with multi-parameter typeclasses and especially flexible instances.
ephemient
+5  A: 

To follow up ephemient's excellent answer: Haskell type classes use an open-world assumption: some idiot can come along later and add an instance declaration that's not a duplicate and yet overlaps with your instance. Think of it as an adversary game: if an adversary can make your program ambiguous, the compiler bleats.

If you're using GHC you can of course say to the compiler "to hell with your paranoia; allow me my ambiguous instance declaration":

{-# LANGUAGE OverlappingInstances #-}

If later evolution of your program leads to overload resolution you didn't expect, the compiler gets 1,000 I-told-you-so points :-)

Norman Ramsey
+1 for excellent wording.
Tom Lokhorst
thanks! following your input I have managed to do it!
yairchu
OverlappingInstances are waaay far down on my list of extensions (even further than UndecidableInstances, which merely makes the compiler's job much harder) to use -- not only unportable, but also breaking the safety guarantees that Haskell normally provides. I'd advise OP to suck it up and deal with `lift`ing or not `lift`ing manually, rather than to add this hack in... but that's my opinion.
ephemient
@ephemient: like you, I appreciate my safety guarantees. I hope my answer made OP's vulnerability clear...
Norman Ramsey