Cycles in class declaration (via superclasses) sometimes make sense.
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. =/