[GHC] #13674: GHC doesn't discharge heterogeneous equality constraint when it ought to

GHC ghc-devs at haskell.org
Thu May 11 12:19:54 UTC 2017


#13674: GHC doesn't discharge heterogeneous equality constraint when it ought to
-------------------------------------+-------------------------------------
        Reporter:  RyanGlScott       |                Owner:  (none)
            Type:  bug               |               Status:  new
        Priority:  normal            |            Milestone:
       Component:  Compiler (Type    |              Version:  8.0.1
  checker)                           |
      Resolution:                    |             Keywords:  TypeInType
Operating System:  Unknown/Multiple  |         Architecture:
 Type of failure:  GHC rejects       |  Unknown/Multiple
  valid program                      |            Test Case:
      Blocked By:                    |             Blocking:
 Related Tickets:                    |  Differential Rev(s):
       Wiki Page:                    |
-------------------------------------+-------------------------------------

Comment (by RyanGlScott):

 Actually, I'm not sure if I find this occurs check explanation entirely
 satisfactory. SkorikGG points out
 [https://github.com/ekmett/constraints/issues/55#issuecomment-300767277
 here] that you can make this program work with a slight tweak:

 {{{#!hs
 {-# LANGUAGE ConstraintKinds #-}
 {-# LANGUAGE DataKinds #-}
 {-# LANGUAGE GADTs #-}
 {-# LANGUAGE PolyKinds #-}
 {-# LANGUAGE RankNTypes #-}
 {-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE TypeApplications #-}
 {-# LANGUAGE TypeFamilies #-}
 {-# LANGUAGE TypeOperators #-}

 import Data.Proxy
 import Data.Type.Equality
 import GHC.Exts (Constraint)
 import GHC.TypeLits
 import Unsafe.Coerce (unsafeCoerce)

 data Dict :: Constraint -> * where
   Dict :: a => Dict a

 infixr 9 :-
 newtype a :- b = Sub (a => Dict b)

 -- | Given that @a :- b@, derive something that needs a context @b@, using
 the context @a@
 (\\) :: a => (b => r) -> (a :- b) -> r
 r \\ Sub Dict = r

 newtype Magic n = Magic (KnownNat n => Dict (KnownNat n))

 magic :: forall n m o. (Integer -> Integer -> Integer) -> (KnownNat n,
 KnownNat m) :- KnownNat o
 magic f = Sub $ unsafeCoerce (Magic Dict) (natVal (Proxy :: Proxy n) `f`
 natVal (Proxy :: Proxy m))

 type family Lcm :: Nat -> Nat -> Nat where

 axiom :: forall a b. Dict (a ~ b)
 axiom = unsafeCoerce (Dict :: Dict (a ~ a))

 lcmNat :: forall n m. (KnownNat n, KnownNat m) :- KnownNat (Lcm n m)
 lcmNat = magic lcm

 lcmIsIdempotent :: forall n. Dict (Lcm n n ~ n)
 lcmIsIdempotent = axiom

 newtype GF (n :: Nat) = GF Integer

 instance KnownNat n => Num (GF n) where
   xf@(GF x) + GF y = GF $ (x+y) `mod` (natVal xf)
   xf@(GF x) - GF y = GF $ (x-y) `mod` (natVal xf)
   xf@(GF x) * GF y = GF $ (x*y) `mod` (natVal xf)
   abs = id
   signum xf@(GF x) | x==0 = xf
                    | otherwise = GF 1
   fromInteger = GF

 x :: GF 5
 x = GF 3

 y :: GF 5
 y = GF 4

 foo :: (KnownNat m, KnownNat n) => GF m -> GF n -> GF (Lcm m n)
 foo m@(GF x) n@(GF y) = GF $ (x*y) `mod` (lcm (natVal m) (natVal n))

 dictToRefl :: Dict (a ~ b) -> a :~: b
 dictToRefl Dict = Refl

 bar :: (KnownNat m)  => GF m -> GF m -> GF m
 bar (x :: GF m) y = castWith (apply Refl $ dictToRefl (lcmIsIdempotent ::
 Dict (Lcm m m ~ m)))
                         (foo x y - foo y x) \\ lcmNat @m @m

 z :: GF 5
 z = bar x y
 }}}

 Notice that in `bar`, we're using a trick of converting the
 `lcmIsIdempotent` `Dict` to a `(:~:)` using `dictToRefl`. And it's clear
 that we are using the fact that `Lcm m m ~ m` in order for `bar` to
 typecheck, but this time, GHC accepts it! Should it?

--
Ticket URL: <http://ghc.haskell.org/trac/ghc/ticket/13674#comment:7>
GHC <http://www.haskell.org/ghc/>
The Glasgow Haskell Compiler


More information about the ghc-tickets mailing list