Skip to content

GitLab

  • Menu
Projects Groups Snippets
  • Help
    • Help
    • Support
    • Community forum
    • Submit feedback
  • Sign in / Register
  • GHC GHC
  • Project information
    • Project information
    • Activity
    • Labels
    • Members
  • Repository
    • Repository
    • Files
    • Commits
    • Branches
    • Tags
    • Contributors
    • Graph
    • Compare
    • Locked Files
  • Issues 4,868
    • Issues 4,868
    • List
    • Boards
    • Service Desk
    • Milestones
    • Iterations
  • Merge requests 456
    • Merge requests 456
  • CI/CD
    • CI/CD
    • Pipelines
    • Jobs
    • Schedules
    • Test Cases
  • Deployments
    • Deployments
    • Releases
  • Analytics
    • Analytics
    • Value stream
    • CI/CD
    • Code review
    • Insights
    • Issue
    • Repository
  • Wiki
    • Wiki
  • Snippets
    • Snippets
  • Activity
  • Graph
  • Create a new issue
  • Jobs
  • Commits
  • Issue Boards
Collapse sidebar
  • Glasgow Haskell Compiler
  • GHCGHC
  • Issues
  • #19568
Closed
Open
Created Mar 20, 2021 by Alexis King@lexi.lambda

Improvement for closed type families based on equation apartness

Closed type families seem to expose as-yet-unexploited opportunities for constraint improvement, one of which I ran into today in the wild. Consider the usual definition of Zip over type level lists:

type family Zip as bs = r | r -> as bs where
  Zip '[]       '[]       = '[]
  Zip (a ': as) (b ': bs) = '(a, b) ': Zip as bs

This type family has an interesting symmetry that could, in theory, be exploited for improvement:

  • If we have a constraint [W] a ~ Zip b '[], then we can produce the improvement [D] b ~ '[], since the type family cannot possibly reduce otherwise.

  • Similarly, if we have a constraint [W] a ~ Zip b (c ': d), we can produce the improvement [D] b ~ (e ': f), for the same reasons.

Sadly, this does not occur in practice. Consider the following self-contained Haskell program:

{-# LANGUAGE AllowAmbiguousTypes, DataKinds, PolyKinds, ScopedTypeVariables, TypeApplications, TypeFamilies, TypeFamilyDependencies, TypeOperators, UndecidableInstances #-}
module M where

type family Zip as bs = r | r -> as bs where
  Zip '[]       '[]       = '[]
  Zip (a ': as) (b ': bs) = '(a, b) ': Zip as bs

class Null (xs :: [()]) where
  nullT :: Bool
instance Null '[] where
  nullT = True
instance Null (x ': xs) where
  nullT = False

foo :: forall a b. (a ~ Zip b '[], Null b) => Bool
foo = nullT @b

bar :: Bool
bar = foo

On GHC 8.10.2, the above program is rejected with an error:

M.hs:19:7: error:
    • Ambiguous type variable ‘b0’ arising from a use of ‘foo’
      prevents the constraint ‘(Null b0)’ from being solved.
      Probable fix: use a type annotation to specify what ‘b0’ should be.
      These potential instances exist:
        instance Null (x : xs) -- Defined at M.hs:12:10
        instance Null '[] -- Defined at M.hs:10:10

This is unfortunate, as there is only one way this program can possibly typecheck, and it involves instantiating b0 to '[].

Proposed change

It would be ideal if GHC could detect these situations and apply the aforementioned improvements automatically, in the same way it already does in other situations. Improvements would be generated while solving equality constraints where enough equations can be guaranteed apart to yield useful refinements.

Note that I am only proposing additional improvement, not any new evidence. That means the following modified definition of foo from above would still be rejected:

foo :: forall a b. a ~ Zip b '[] => Bool
foo = nullT @b

This is because b is a skolem within the body of foo, not a metavariable. Therefore, we are not at liberty to instantiate it to anything without suitable evidence. By restricting this proposal to improvement, I think it’s conservative enough that a full GHC proposal would be silly; this is not adding anything fundamental, just making a few more programs typecheck.

Potential problems

There is a lingering fear in the back of my mind, which is that stuck type families might make the criteria under which this type of improvement is sound more complicated. For example, suppose we have a constraint like [W] a ~ Zip b '[], where both a and b are metavariables. In certain situations, my understanding is that it’s possible for the program to typecheck without reducing Zip b '[] if a is not “forced” by some other part of the typechecking process.

If that’s true, then it’s theoretically possible for “improvement” of this type to turn a well-typed program into an ill-typed one. If, without improvement, unification eventually instantiates b := (c ': d), then Zip (c ': d) '[] is certainly stuck. But if a is never needed, that could be okay. In that scenario, improvement would create a type error where previously there was none.

I don’t understand the semantics of stuck type families well enough to fully grasp the potential pitfalls here. It seems to me that even if the above were true, perhaps it could be avoided by restricting the situations where improvement applies. However, I think I’d need someone else to chime in to confirm whether or not that is true.

To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information
Assignee
Assign to
Time tracking