Skip to content

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
To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information