Opened 3 years ago

Closed 18 months ago

#13324 closed feature request (fixed)

Allow PartialTypeSignatures in the instance context of a standalone deriving declaration

Reported by: RyanGlScott Owned by:
Priority: normal Milestone: 8.6.1
Component: Compiler (Type checker) Version: 8.0.1
Keywords: deriving Cc: Iceland_jack
Operating System: Unknown/Multiple Architecture: Unknown/Multiple
Type of failure: None/Unknown Test Case: partial-sigs/should_compile/T13324_compile, partial-sigs/should_fail/T13324_fail1, partial-sigs/should_fail/T13324_fail2
Blocked By: Blocking:
Related Tickets: #10607 Differential Rev(s): Phab:D4383
Wiki Page:

Description

Currently, if you try to use a wildcard anywhere in an instance context, it will fail immediately:

$ ghci
GHCi, version 8.0.2: http://www.haskell.org/ghc/  :? for help
Loaded GHCi configuration from /home/rgscott/.ghci
λ> instance _ => Show (Maybe a)

<interactive>:1:10: error:
    Wildcard ‘_’ not allowed
      in an instance declaration for ‘Show’

But there's one situation where we could lift this restriction: the context of a standalone, derived instance declaration. That is, something like this:

deriving instance _ => Show (Maybe a)

Why? Because GHC already has the machinery needed to infer what the context should be (see this part of TcDerivInfer), so if a user turned on PartialTypeSignatures, GHC could just fill in the wildcard with the inferred constraints.

The implementation won't be that easy, however, since we'd also have to watch out for trickery such as:

deriving instance (C a, _) => C (T a b c)

I only mentioned putting wildcards in a derived instance context, because I think allowing the use of wildcards elsewhere in the instance head might be too difficult to deal with. I mean, how would you fill in this, for example?

instance (_ a)

This mini-feature has a very practical application: it would allow users to wield the flexibility of StandaloneDeriving without having to manually type in the instance context every time. That is, users could type in these instances:

deriving instance _ => Data (T a b c)
deriving instance _ => Eq (T a b c)

Instead of their fully spelled-out, more laborious counterparts. This would be crucial for Template Haskell, as its ability to infer these contexts is quite limited (see #10607 for an example where this cropped up).

Idle thought: could this be generalized to work for the instance context of any instance declaration (and not just derived ones)? From an outside perspective, it seems like typechecking other instances would require inferring constraints in a fashion quite similar to that of derived instances. But I am not at all familiar with that part of the typechecker, so I might be totally off here.

Change History (12)

comment:1 Changed 3 years ago by RyanGlScott

We should make sure to watch out for these two corner cases:

  • Exotic constraints. Currently, trying to derive Eq like this:
newtype Foo f a = Foo (f (f a)) deriving Eq

Will be rejected with an error message saying:

  • No instance for (Eq (f (f a)))
      arising from the 'deriving' clause of a data type declaration
    Possible fix:
      use a standalone 'deriving instance' declaration,
        so you can specify the instance context yourself

That is, GHC will not put Eq (f (f a)) into the set of inferred constraints because it is too "exotic". But presumably, we want a similar failure if the user instead typed this:

newtype Foo f a = Foo (f (f a))
deriving instance _ => Eq (Foo f a)

Since it's morally equivalent to just using a deriving Eq clause.

  • GADTs. GHC chooses not to even make an attempt if you try to stock-derive certain instances for GADTs with a deriving clause. For instance, this:
data T a where
    MkT :: T Int
  deriving Eq

is rejected with:

  • Can't make a derived instance of ‘Eq (T a)’:
      Constructor ‘MkT’ has existentials or constraints in its type
      Possible fix: use a standalone deriving declaration instead
  • In the data declaration for ‘T’

So as a consequence, we should probably disallow this as well:

data T a where
    MkT :: T Int
deriving instance _ => Eq (T a)

Notice that in both cases, the error message suggests trying a standalone deriving declaration! Obviously, we shouldn't do that if a user tries the equivalent, standalone approach with wildcards, so we should come up with a more appropriate error message for this scenario.

comment:2 Changed 3 years ago by simonpj

Since it's morally equivalent to just using a deriving Eq clause.

And it'll be implementationally the same as using deriving Eq too! So the same things will happen automatically.

In the code, the mtheta variable will tbe Nothing rather than Just.

Just allowing a wildcard on its own would be a good start. But we have code for doing the "subtraction" in cases like (Eq a, _) => blah, so it would not be too hard to support that too.

comment:3 Changed 3 years ago by bgamari

Instead of their fully spelled-out, more laborious counterparts. This would be crucial for Template Haskell, as its ability to infer these contexts is quite limited (see #10607 for an example where this cropped up).

It seems like for this to be useful we will want the ability to more precisely apply PartialTypeSignatures (in particular from splices) to ensure that users are forced to enable it for their entire module to avoid spurious errors. Is there a ticket for this yet?

comment:4 Changed 3 years ago by RyanGlScott

bgamari, yes, I believe #8510 is what you are looking for. I don't think that implementing this feature would be that difficult, either, since:

  1. We already have a Extension datatype for representing all GHC extensions
  2. The GHC API already has setXOptM/unsetXOptM for enabling/disabling Extensions within the scope of some computation

So I think we'd just need a Template Haskell function qWithExtensions :: Quasi m => [OnOff Extension] -> m a -> m a that enabled/disables whatever the user provides. Then libraries like deriveTopDown could just use qWithExtensions [On PartialTypeSignatures] to wrap any splices that contain wildcard types.

comment:5 Changed 3 years ago by simonpj

Do you also, as #13415 implies, want the non-deriving case to work?

instance _ => C (T a) where
  op x = ...
  bop y = ...

This would be more ambitious, because you'd have to infer the constraints for the arbitrary user-written code in "...", and TcInstDcls isn't set up to do that.

Remember that two instance declarations might both need each other. That's why there's a fixpoint in the deriving code.

I think it'd be a lot easier to handle the deriving case than the general case.

comment:6 in reply to:  5 Changed 3 years ago by RyanGlScott

Replying to simonpj:

Do you also, as #13415 implies, want the non-deriving case to work?

Indeed, I think that if I can figure out how to make it work for deriving, I don't think inferring constraints for arbitrary instances would be that fundamentally different in operation.

Granted, I don't think I'm going to knock out both at the same time. Baby steps and all.

comment:7 Changed 2 years ago by RyanGlScott

Keywords: deriving added

comment:8 Changed 2 years ago by Iceland_jack

Cc: Iceland_jack added

comment:9 Changed 20 months ago by RyanGlScott

Differential Rev(s): Phab:D4383
Status: newpatch

comment:10 Changed 19 months ago by Simon Peyton Jones <simonpj@…>

In 1c062b79/ghc:

Simplify rnLHsInstType

This patch is preparatory for the main fix for Trac #13324

Here, we simplify rnLHsInstType so that it does not try
to figure out the class name.  This turns out to have a good
(rather than bad) effect on error messages, and it prepares
the way for the main event.

Plus, less code!

comment:11 Changed 18 months ago by Ryan Scott <ryan.gl.scott@…>

In affdea82/ghc:

Allow PartialTypeSignatures in standalone deriving contexts

Summary:
At its core, this patch is a simple tweak that allows a user
to write:

```lang=haskell
deriving instance _ => Eq (Foo a)
```

Which is functionally equivalent to:

```lang=haskell
data Foo a = ...
  deriving Eq
```

But with the added flexibility that `StandaloneDeriving` gives you
(namely, the ability to use it anywhere, not just in the same module
that `Foo` was declared in). This fixes #13324, and should hopefully
address a use case brought up in #10607.

Currently, only the use of a single, extra-constraints wildcard is
permitted in a standalone deriving declaration. Any other wildcard
is rejected, so things like
`deriving instance (Eq a, _) => Eq (Foo a)` are currently forbidden.

There are quite a few knock-on changes brought on by this change:

* The `HsSyn` type used to represent standalone-derived instances
  was previously `LHsSigType`, which isn't sufficient to hold
  wildcard types. This needed to be changed to `LHsSigWcType` as a
  result.

* Previously, `DerivContext` was a simple type synonym for
  `Maybe ThetaType`, under the assumption that you'd only ever be in
  the `Nothing` case if you were in a `deriving` clause. After this
  patch, that assumption no longer holds true, as you can also be
  in this situation with standalone deriving when an
  extra-constraints wildcard is used.

  As a result, I changed `DerivContext` to be a proper datatype that
  reflects the new wrinkle that this patch adds, and plumbed this
  through the relevant parts of `TcDeriv` and friends.

* Relatedly, the error-reporting machinery in `TcErrors` also assumed
  that if you have any unsolved constraints in a derived instance,
  then you should be able to fix it by switching over to standalone
  deriving. This was always sound advice before, but with this new
  feature, it's possible to have unsolved constraints even when
  you're standalone-deriving something!

  To rectify this, I tweaked some constructors of `CtOrigin` a bit
  to reflect this new subtlety.

This requires updating the Haddock submodule. See my fork at
https://github.com/RyanGlScott/haddock/commit/067d52fd4be15a1842cbb05f42d9d482de0ad3a7

Test Plan: ./validate

Reviewers: simonpj, goldfire, bgamari

Reviewed By: simonpj

Subscribers: goldfire, rwbarton, thomie, mpickering, carter

GHC Trac Issues: #13324

Differential Revision: https://phabricator.haskell.org/D4383

comment:12 Changed 18 months ago by RyanGlScott

Milestone: 8.6.1
Resolution: fixed
Status: patchclosed
Test Case: partial-sigs/should_compile/T13324_compile, partial-sigs/should_fail/T13324_fail1, partial-sigs/should_fail/T13324_fail2
Note: See TracTickets for help on using tickets.