Can I magic up type equality from a functional dependency?

后端 未结 2 1212
忘掉有多难
忘掉有多难 2020-12-16 12:37

I\'m trying to get some sense of MultiParamTypeClasses and FunctionalDependencies, and the following struck me as an obvious thing to try:

相关标签:
2条回答
  • 2020-12-16 12:41

    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!

    0 讨论(0)
  • 2020-12-16 12:48

    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.

    0 讨论(0)
提交回复
热议问题