[GHC] #15347: QuantifiedConstraints: Implication constraints with type families don't work
GHC
ghc-devs at haskell.org
Tue Jul 24 21:35:15 UTC 2018
#15347: QuantifiedConstraints: Implication constraints with type families don't
work
-------------------------------------+-------------------------------------
Reporter: aaronvargo | Owner: (none)
Type: bug | Status: new
Priority: normal | Milestone:
Component: Compiler (Type | Version: 8.5
checker) | Keywords:
Resolution: | QuantifiedConstraints
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 aaronvargo):
Replying to [comment:10 simonpj]:
A few things:
First, there's probably another way to do this without a free-variable
analysis: float type families out when they appear in wanted constraints.
E.g. in `foo`, the floating would happen because `F b` appears in the
wanted constraint, not because it's independent of the forall'd variables
in the given quantified constraint. Would something like that be more
feasible?
Second,
> Consider this:
> {{{
> type family F a
> type instance F Int = Bool
>
> instance Eq a => Eq (F a) where ...
> }}}
> Should we allow this? Obviously not. It's like allowing...
Indeed, but this is an argument for disallowing ambiguity, not for
disallowing type families. In your `Eq` example, `a` is ambiguous since
`F` may not be injective. (likewise, `xs` and `ys` are ambiguous in the
`map` example since `(++)` isn't injective). Not all uses of type families
cause such ambiguity, and the compiler already has a perfectly good
ambiguity check which can determine whether they do. Some cases where they
don't are:
1. The type family is independent of the quantified variables
2. The type family is injective
3. The type family can be reduced to an expression which is unambiguous
(this is #13262)
4. The quantified variables appear ambiguously in the type family, but are
still made unambiguous by the rest of the instance head (e.g. `forall a. C
(F a) a`)
For top-level instances there may be coherence issues with allowing type
families even when they don't cause ambiguity (though I'm not sure that
there are), but such issues don't apply to QCs, which can't introduce
incoherence.
So why not allow type families in QCs, but require that the quantified
variables be unambiguous?
Third, working around this by manually floating out type families can be
hugely painful, as it requires sacrificing one of the major benefits of
type families, namely the ability to avoid passing around extra class
parameters which are already determined by other parameters. The basic
problem is this: suppose we have a class:
{{{#!hs
class (forall a. Eq a => Eq (f a)) => C f t | t -> f
}}}
Currently, the parameter `f` can never be instantiated with a type family
without breaking the quantified constraint. This means the extra parameter
needs to be repeated throughout the code for the QC to continue to work,
despite the fact that `f` is already determined by `t`.
Consider the following encoding of categories and functors, which makes
use of type families:
{{{#!hs
type Hom k = k -> k -> Type
-- Natural transformations
data (==>) (p :: Hom i) (q :: Hom j) (f :: i -> j) (g :: i -> j)
class ( Category (Op p)
, Op (Op p) ~ p
, Ob (Op p) ~ Ob p
, FunctorOf' (Op p) (p ==> (->)) p
, forall a. Ob p a => FunctorOf' p (->) (p a)
) => Category (p :: Hom k) where
type Ob p :: k -> Constraint
type Op p :: Hom k
-- FunctorOf without superclasses, to work around issues with
UndecidableSuperClasses
class FunctorOf' p q (f :: i -> j) | f -> p q
-- FunctorOf' with proper superclasses
class ( FunctorOf' p q f
, Category p
, Category q
, forall a. Ob p a => Ob q (f a)
) => FunctorOf p q f
instance ( FunctorOf' p q f
, Category p
, Category q
, forall a. Ob p a => Ob q (f a)
) => FunctorOf p q f
}}}
Note that `Category p` has a superclass `Category (Op p)`, which has a
superclass:
{{{#!hs
-- noting that Ob (Op p) ~ Ob p
forall a. Ob p a => FunctorOf' (Op p) (->) (Op p a)
}}}
and that the superclasses of `FunctorOf p q f` include:
{{{#!hs
forall a. Ob p a => Ob q (f a)
forall a. Ob p a => FunctorOf' (Op p) (->) (Op p a)
forall a. Ob q a => FunctorOf' (Op q) (->) (Op q a)
}}}
All of these have independent type families in the heads. Floating them
out manually requires doubling the number of class parameters for both
`Category` and `FunctorOf`:
{{{#!hs
class (..., op ~ Op p) => Category op p where ...
class ( FunctorOf' p q f
, Category opP p
, Category opQ q
, obQ ~ Ob q
, forall a. Ob p a => obQ (f a)
) => FunctorOf opP p opQ q obQ f
}}}
These parameters will spread throughout all of the code. Subclasses will
add yet more parameters, and will never be able to use type families for
anything which is a functor or a category, without sacrificing some QCs.
Types will be lost in a sea of parameters.
This ends up being more painful than just using `Dict`s.
--
Ticket URL: <http://ghc.haskell.org/trac/ghc/ticket/15347#comment:13>
GHC <http://www.haskell.org/ghc/>
The Glasgow Haskell Compiler
More information about the ghc-tickets
mailing list