Skip to content

GitLab

  • Projects
  • Groups
  • Snippets
  • Help
    • Loading...
  • Help
    • Help
    • Support
    • Community forum
    • Submit feedback
  • Sign in / Register
GHC
GHC
  • Project overview
    • Project overview
    • Details
    • Activity
    • Releases
  • Repository
    • Repository
    • Files
    • Commits
    • Branches
    • Tags
    • Contributors
    • Graph
    • Compare
    • Locked Files
  • Issues 4,262
    • Issues 4,262
    • List
    • Boards
    • Labels
    • Service Desk
    • Milestones
    • Iterations
  • Merge Requests 419
    • Merge Requests 419
  • Requirements
    • Requirements
    • List
  • CI / CD
    • CI / CD
    • Pipelines
    • Jobs
    • Schedules
  • Security & Compliance
    • Security & Compliance
    • Dependency List
    • License Compliance
  • Operations
    • Operations
    • Incidents
    • Environments
  • Analytics
    • Analytics
    • CI / CD
    • Code Review
    • Insights
    • Issue
    • Repository
    • Value Stream
  • Wiki
    • Wiki
  • Snippets
    • Snippets
  • Members
    • Members
  • Collapse sidebar
  • Activity
  • Graph
  • Create a new issue
  • Jobs
  • Commits
  • Issue Boards
  • Glasgow Haskell Compiler
  • GHCGHC
  • Issues
  • #10318

Closed
Open
Opened Apr 17, 2015 by Edward Kmett@ekmett

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. =/

Edited Mar 10, 2019 by Ben Gamari
Assignee
Assign to
8.0.1
Milestone
8.0.1 (Past due)
Assign milestone
Time tracking
None
Due date
None
Reference: ghc/ghc#10318