Weird interaction between fundeps and overlappable instances
Consider this code
class MyState s m | m -> s where
getMyState :: m s
instance {-# OVERLAPPABLE #-} (MyState s m, MonadTrans t, Monad m) => MyState s (t m) where
getMyState = lift getMyState
instance Monad m => MyState s (StateT s m) where
getMyState = get
f :: (MyState Int m, MyState Char m, MonadIO m) => m ()
f = do
int <- getMyState
str <- getMyState
liftIO $ putStrLn (replicate int str)
works1 :: (MyState s m, Show s, MonadIO m) => m ()
works1 = do
a <- getMyState
liftIO (print a)
works2 = runStateT (runStateT f (5 :: Int)) 'a'
It defines a class similar to MonadState
of mtl. There is a functional dependency in place, just like with MonadState
and we can see that it works the same because works1
compiles where a
would have an ambiguous type otherwise.
The f
function "shouldn't" compile because it's using two different states at once subverting the functional dependency restriction. It does however compile because an explicit type signature is provided with an unsolvable constraint.
Now the really weird part is that works2
also compiles and produces the expected result even though it's using f
.
Here's what I think is happening: instance resolution is looking for MyState Int (StateT Char m)
and it finds the MyState s (StateT s m)
instance. Instead of complaining that Int
doesn't match Char
(due to the fundep), it just rejects the instance and takes the overlappable one that does match. In the case where the state is unknown (i.e. both instances match), the fundep kicks in. That's why runStateT works1 True
works.
Is this intended behavior? It seems pretty useful in some situations and I've tested this with 8.2 and 8.6 with the same results.
Trac metadata
Trac field | Value |
---|---|
Version | 8.6.2 |
Type | Bug |
TypeOfFailure | OtherFailure |
Priority | normal |
Resolution | Unresolved |
Component | Compiler |
Test case | |
Differential revisions | |
BlockedBy | |
Related | |
Blocking | |
CC | |
Operating system | |
Architecture |