I\'m trying to get some sense of MultiParamTypeClasses
and FunctionalDependencies
, and the following struck me as an obvious thing to try:
You don't need to resort to multiple modules to fool the functional dependency checker. Here are two examples of wrong fundeps that still build with HEAD. They are adapted from the GHC test suite.
{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies,
FlexibleInstances, FlexibleContexts,
UndecidableInstances, DataKinds, PolyKinds,
GADTs #-}
module M where
data K x a = K x
class Het a b | a -> b where
het :: m (f c) -> a -> m b
instance Het t t where het = undefined
class GHet (a :: * -> *) (b :: * -> *) | a -> b
instance GHet (K a) (K [a])
instance Het a b => GHet (K a) (K b)
data HBool = HFalse | HTrue
class TypeEq x y b | x y -> b
instance {-# OVERLAPS #-} (HTrue ~ b) => TypeEq x x b
instance {-# OVERLAPS #-} (HFalse ~ b) => TypeEq x y b
The fundep checker is still much better than it used to be!
I don't think this fact (as stated by the type of fob
) is actually true. Due to the open world property of type classes, you can violate the fundep with module boundaries.
This is show by the following example. This code has only been tested with GHC 7.10.3 (fundeps were massively broken in older versions - don't know what happens then). Assume that you can indeed implement the following:
module A
(module A
,module Data.Type.Equality
,module Data.Proxy
)where
import Data.Type.Equality
import Data.Proxy
class C a b | a -> b
inj_C :: (C a b, C a b') => Proxy a -> b :~: b'
inj_C = error "oops"
Then a few more modules:
module C where
import A
instance C () Int
testC :: C () b => Int :~: b
testC = inj_C (Proxy :: Proxy ())
and
module B where
import A
instance C () Bool
testB :: C () b => b :~: Bool
testB = inj_C (Proxy :: Proxy ())
and
module D where
import A
import B
import C
oops :: Int :~: Bool
oops = testB
oops_again :: Int :~: Bool
oops_again = testC
Int :~: Bool
is clearly not true, so by contradiction, inj_C
cannot exist.
I believe you can still safely write inj_C
with unsafeCoerce
if you don't export the class C
from the module where it's defined. I've used this technique, and have extensively tried, and not been able to write a contradiction. Not to say it's impossible, but at least very difficult and a rare edge case.