Inferring instance constraints with DeriveAnyClass

Andres, I'm trying to think of a proper solution to Trac #12144 [1]. This bug triggers when you try to use DeriveAnyClass in a somewhat exotic fashion: {-# LANGUAGE DeriveAnyClass, KindSignatures #-} class C (a :: * -> *) data T a = MkT (a -> Int) deriving C This currently gives a GHC panic: ghc: panic! (the 'impossible' happened) (GHC version 8.0.1 for x86_64-unknown-linux): contravariant This baffled me until I realized why it's happening: for typeclasses of kind * -> *, DeriveAnyClass simply re-uses the same algorithm that DeriveFunctor uses for coming up with an instance context. For instance, if you have: data T f a = MkT a (f a) deriving (Functor, C) Then it would generate two instances of the form: instance Functor f => Functor (T f) where ... instance C f => C (T f) where ... But #12144 reveals the fatal downside of doing this: DeriveFunctor has special knowledge about type parameters in contravariant positions, but this doesn't even make sense to think about with a class like C! (The only reason GHC won't panic if a Functor instance is derived for T is because there are Functor-specific checks that cause an error message to pop up before the panic can be reached.) My question is then: why does DeriveAnyClass take the bizarre approach of co-opting the DeriveFunctor algorithm? Andres, you originally proposed this in #7346 [2], but I don't quite understand why you wanted to do it this way. Couldn't we infer the context simply from the contexts of the default method type signatures? This is a question that Reid Barton has also asked, to which José Pedro Magalhães answered in the negatory [3]. But Pedro's reasoning has never quite made sense to me, because we've been able to typecheck constraints arising from default method type signatures for a long time, so why would it be impractical to do so in this case? I'd appreciate hearing a more detailed explanation on this issue, because at the moment, I am completely stuck on figuring out how one might fix #12144. Regards, Ryan S. ----- [1] https://ghc.haskell.org/trac/ghc/ticket/12144 [2] https://ghc.haskell.org/trac/ghc/ticket/7346 [3] https://ghc.haskell.org/trac/ghc/ticket/5462#comment:30

| My question is then: why does DeriveAnyClass take the bizarre approach | of co-opting the DeriveFunctor algorithm? Andres, you originally | proposed this in #7346 [2], but I don't quite understand why you | wanted to do it this way. Couldn't we infer the context simply from | the contexts of the default method type signatures? That last suggestion makes perfect sense to me. After all, we are going to generate an instance looking like instance .. => C (T a) where op1 = <default-op1> op2 = <default-op2> so all we need in ".." is enough context to satisfy the needs of <default-op1> etc. Well, you need to take account of the class op type sig too: class C a where op :: Eq a => a -> a default op :: (Eq a, Show a) => a -> a We effectively define default_op :: (Eq a, Show a) => a -> a Now with DeriveAnyClass for lists, we effectively get instance ... => C [a] where op = default_op What is ..? Well, we need (Eq [a], Show [a]); but we are given Eq [a] (because that's op's instantiated type. So Show a is all we need in the end. Simon

I still don't think you can do it just from the default method's type. A
typical case is the following:
class C a where
op :: a -> Int
default op :: (Generic a, GC (Rep a)) => a -> Int
When giving an instance C [a], you might well find out that you need C a
=>, but this is not something
you can see in the type of the default method; it follows only after the
expansion of Rep [a] and resolving
the GC constraint a number of times.
Best regards,
Pedro
On Fri, Jun 17, 2016 at 12:43 PM, Simon Peyton Jones
| My question is then: why does DeriveAnyClass take the bizarre approach | of co-opting the DeriveFunctor algorithm? Andres, you originally | proposed this in #7346 [2], but I don't quite understand why you | wanted to do it this way. Couldn't we infer the context simply from | the contexts of the default method type signatures?
That last suggestion makes perfect sense to me. After all, we are going to generate an instance looking like
instance .. => C (T a) where op1 = <default-op1> op2 = <default-op2>
so all we need in ".." is enough context to satisfy the needs of <default-op1> etc.
Well, you need to take account of the class op type sig too:
class C a where op :: Eq a => a -> a default op :: (Eq a, Show a) => a -> a
We effectively define default_op :: (Eq a, Show a) => a -> a
Now with DeriveAnyClass for lists, we effectively get
instance ... => C [a] where op = default_op
What is ..? Well, we need (Eq [a], Show [a]); but we are given Eq [a] (because that's op's instantiated type. So Show a is all we need in the end.
Simon _______________________________________________ ghc-devs mailing list ghc-devs@haskell.org http://mail.haskell.org/cgi-bin/mailman/listinfo/ghc-devs

Yes, but none of that has anything to do with a walk over the data type, as deriving(Functor) does!
You are right that what we need is the result of simplifying the instantiated constraint
(Generic [a], GC (Rep [a]))
Simplify that constraint (simplifyDeriv does that), including reducing type-function applications, and that’s your context.
But no need to look at the data type’s constructors, as deriving(Functor) does.
Simon
From: josepedromagalhaes@gmail.com [mailto:josepedromagalhaes@gmail.com] On Behalf Of José Pedro Magalhães
Sent: 18 June 2016 09:16
To: Simon Peyton Jones

On Sat, Jun 18, 2016 at 12:51 PM, Simon Peyton Jones
But no need to look at the data type’s constructors, as deriving(Functor) does.
Yes, that's right. I believe we've used the "derive Functor" strategy for inferring constraints simply because all generic functions (over Generic1) that we had in mind at the time were Functor-like, so that was an appropriate first solution. But I totally agree that it can be improved! Best regards, Pedro
Simon
*From:* josepedromagalhaes@gmail.com [mailto:josepedromagalhaes@gmail.com] *On Behalf Of *José Pedro Magalhães *Sent:* 18 June 2016 09:16 *To:* Simon Peyton Jones
*Cc:* Ryan Scott ; Andres Löh < andres.loeh@gmail.com>; GHC developers *Subject:* Re: Inferring instance constraints with DeriveAnyClass I still don't think you can do it just from the default method's type. A typical case is the following:
class C a where
op :: a -> Int
default op :: (Generic a, GC (Rep a)) => a -> Int
When giving an instance C [a], you might well find out that you need C a =>, but this is not something
you can see in the type of the default method; it follows only after the expansion of Rep [a] and resolving
the GC constraint a number of times.
Best regards,
Pedro
On Fri, Jun 17, 2016 at 12:43 PM, Simon Peyton Jones < simonpj@microsoft.com> wrote:
| My question is then: why does DeriveAnyClass take the bizarre approach | of co-opting the DeriveFunctor algorithm? Andres, you originally | proposed this in #7346 [2], but I don't quite understand why you | wanted to do it this way. Couldn't we infer the context simply from | the contexts of the default method type signatures?
That last suggestion makes perfect sense to me. After all, we are going to generate an instance looking like
instance .. => C (T a) where op1 = <default-op1> op2 = <default-op2>
so all we need in ".." is enough context to satisfy the needs of <default-op1> etc.
Well, you need to take account of the class op type sig too:
class C a where op :: Eq a => a -> a default op :: (Eq a, Show a) => a -> a
We effectively define default_op :: (Eq a, Show a) => a -> a
Now with DeriveAnyClass for lists, we effectively get
instance ... => C [a] where op = default_op
What is ..? Well, we need (Eq [a], Show [a]); but we are given Eq [a] (because that's op's instantiated type. So Show a is all we need in the end.
Simon _______________________________________________ ghc-devs mailing list ghc-devs@haskell.org http://mail.haskell.org/cgi-bin/mailman/listinfo/ghc-devs https://na01.safelinks.protection.outlook.com/?url=http%3a%2f%2fmail.haskell.org%2fcgi-bin%2fmailman%2flistinfo%2fghc-devs&data=01%7c01%7csimonpj%40064d.mgd.microsoft.com%7cb6b18be0b6ac490d83ae08d39750c6cf%7c72f988bf86f141af91ab2d7cd011db47%7c1&sdata=hXiE2P63QGD4ZAFonWpDvqc0vKX%2fBPgPYVBAjIiaIXw%3d

Sorry for the late reply.
I think the reason I proposed to reuse the algorithm for Functor was
that (A) as Pedro says, it was the class most closely resembling the
classes we wanted to write at the time, and (B) I almost certainly was
not at all aware that there is special magic in the code for
contravariant arguments. In general, I'm not overly eager to try to be
too clever in inferring the right instance constraints. I think
standalone deriving should be used in anything but the most
straight-forward scenarios. If there is a simple and easy-to-specify
way to infer the simple cases properly though, I am certainly not
opposed to it.
Cheers,
Andres
On Sat, Jun 18, 2016 at 1:55 PM, José Pedro Magalhães
On Sat, Jun 18, 2016 at 12:51 PM, Simon Peyton Jones
wrote: But no need to look at the data type’s constructors, as deriving(Functor) does.
Yes, that's right.
I believe we've used the "derive Functor" strategy for inferring constraints simply because all generic functions (over Generic1) that we had in mind at the time were Functor-like, so that was an appropriate first solution. But I totally agree that it can be improved!
Best regards, Pedro
Simon
From: josepedromagalhaes@gmail.com [mailto:josepedromagalhaes@gmail.com] On Behalf Of José Pedro Magalhães Sent: 18 June 2016 09:16 To: Simon Peyton Jones
Cc: Ryan Scott ; Andres Löh ; GHC developers Subject: Re: Inferring instance constraints with DeriveAnyClass I still don't think you can do it just from the default method's type. A typical case is the following:
class C a where
op :: a -> Int
default op :: (Generic a, GC (Rep a)) => a -> Int
When giving an instance C [a], you might well find out that you need C a =>, but this is not something
you can see in the type of the default method; it follows only after the expansion of Rep [a] and resolving
the GC constraint a number of times.
Best regards,
Pedro
On Fri, Jun 17, 2016 at 12:43 PM, Simon Peyton Jones
wrote: | My question is then: why does DeriveAnyClass take the bizarre approach | of co-opting the DeriveFunctor algorithm? Andres, you originally | proposed this in #7346 [2], but I don't quite understand why you | wanted to do it this way. Couldn't we infer the context simply from | the contexts of the default method type signatures?
That last suggestion makes perfect sense to me. After all, we are going to generate an instance looking like
instance .. => C (T a) where op1 = <default-op1> op2 = <default-op2>
so all we need in ".." is enough context to satisfy the needs of <default-op1> etc.
Well, you need to take account of the class op type sig too:
class C a where op :: Eq a => a -> a default op :: (Eq a, Show a) => a -> a
We effectively define default_op :: (Eq a, Show a) => a -> a
Now with DeriveAnyClass for lists, we effectively get
instance ... => C [a] where op = default_op
What is ..? Well, we need (Eq [a], Show [a]); but we are given Eq [a] (because that's op's instantiated type. So Show a is all we need in the end.
Simon _______________________________________________ ghc-devs mailing list ghc-devs@haskell.org http://mail.haskell.org/cgi-bin/mailman/listinfo/ghc-devs

| forward scenarios. If there is a simple and easy-to-specify way to
| infer the simple cases properly though, I am certainly not opposed to
| it.
I think there is a simple easy-to-specify way to infer /all/ cases!
Simon
| -----Original Message-----
| From: Andres Löh [mailto:andres.loeh@gmail.com]
| Sent: 21 June 2016 12:24
| To: Pedro Magalhães (dreixel@gmail.com)
participants (4)
-
Andres Löh
-
José Pedro Magalhães
-
Ryan Scott
-
Simon Peyton Jones