[GHC] #10318: Cycles in class declaration (via superclasses) sometimes make sense.

GHC ghc-devs at haskell.org
Fri Apr 17 01:56:48 UTC 2015


#10318: Cycles in class declaration (via superclasses) sometimes make sense.
-------------------------------------+-------------------------------------
              Reporter:  ekmett      |             Owner:
                  Type:  feature     |            Status:  new
  request                            |         Milestone:
              Priority:  normal      |           Version:  7.10.1
             Component:  Compiler    |  Operating System:  Unknown/Multiple
  (Type checker)                     |   Type of failure:  GHC rejects
              Keywords:              |  valid program
          Architecture:              |        Blocked By:
  Unknown/Multiple                   |   Related Tickets:
             Test Case:              |
              Blocking:              |
Differential Revisions:              |
-------------------------------------+-------------------------------------
 I'd like to be able to say the following, to describe the notion of an
 integral domain in Haskell:

 {{{

 -- | Product of non-zero elements always non-zero.
 -- Every integral domain has a field of fractions.
 -- The field of fractions of any field is itself.
 class (Frac (Frac a) ~ Frac a, Fractional (Frac a), IntegralDomain (Frac
 a))
   => IntegralDomain a where
   type Frac a :: *
   embed :: a -> Frac a

 instance IntegralDomain Integer where
   type Frac Integer = Rational
   embed = fromInteger

 instance IntegralDomain Rational where
   type Frac Rational = Rational
   embed = id
 }}}

 But GHC gets scared when it sees the cyclic reference that
 `IntegralDomain` instances depend on an IntegralDomain superclass, which
 really is cyclic in the (Frac a) case here, and that is kind of the point.
 =)

 Right now the best approximation of the correct answer that I have for
 this situation is to lie and claim the constraint is weaker:

 {{{
 -- | Product of non-zero elements always non-zero
 class (Frac (Frac a) ~ Frac a, Fractional (Frac a)) =>
 AlmostIntegralDomain a where
   type Frac a :: *
   embed :: a -> Frac a

 class (AlmostIntegralDomain a, AlmostIntegralDomain (Frac a)) =>
 IntegralDomain a
 instance (AlmostIntegralDomain a, AlmostIntegralDomain (Frac a)) =>
 IntegralDomain a

 instance AlmostIntegralDomain Integer where
   type Frac Integer = Rational
   embed = fromInteger

 instance AlmostIntegralDomain Rational where
   type Frac Rational = Rational
   embed = id
 }}}

 Now the user is stuck defining a different class than the one they
 consume.

 Alternately, with `ConstraintKinds`, I can encode:

 {{{
 data Dict p where
   Dict :: p => Dict p

 class (Frac (Frac a) ~ Frac a, Fractional (Frac a)) => IntegralDomain a
 where
   type Frac a :: *
   embed :: a -> Frac a
   proofFracIsIntegral :: p a -> Dict (IntegralDomain (Frac a))
   default proofFracIsIntegral :: IntegralDomain (Frac a) => p a -> Dict
 (IntegralDomain (Frac a))
   proofFracIsIntegral _ = Dict
 }}}

 but now whenever I need to get from `IntegralDomain a`  to `IntegralDomain
 (Frac a)` I need to explicitly open the `proofFracIsIntegral` with a rats'
 nest of `ScopedTypeVariables`.

 It would be really really nice if I could get GHC to deal with this for me
 as I currently have a few thousand lines of code hacking around this
 limitation. =/

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


More information about the ghc-tickets mailing list