Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Quantify class methods' kind variables in the correct order using TypeAbstractions #596

Merged
merged 5 commits into from
Jun 6, 2024

Conversation

RyanGlScott
Copy link
Collaborator

When promoting a class with a standalone kind signature, it is possible to ensure that the promoted methods' kind variables are quantified in the same order as the original method's type variables. This is now possible thanks to the TypeAbstractions language extension, which lets singletons-th pick the exact order of kind variables in the promoted methods' associated type families using @ binders. For the full details of how this is accomplished, see the expanded Note [Promoted class methods and kind variable ordering] in D.S.TH.Promote (specifically, Case 1: Parent class with standalone kind signature).

As it turns out, many of the steps we need to perform to achieve this are the same steps that the singDataSAK function performs when producing a standalone kind signature for a singled data declaration. As such, I factored out the common bits into a helper function called matchUpSAKWithDecl in D.S.TH.Util.

Fixes #589.

In a subsequent commit, we will want to use these functions in D.S.TH.Promote,
which was not possible when these functions lived in D.S.TH.Single.Data.
…a types

`matchUpSigWithDecl` is useful for any sort of type-level declaration, not just
data types. Moreover, in a subsequent commit we will actually use
`matchUpSigWithDecl` on a class declaration instead of a data type declaration.
This patch ensures that `matchUpSigWithDecl`'s comments and argument names
aren't overfitted to data types.
There is no reason to specialize this to `DTyVarBndrSpec`, as this works for
any `flag` type.
For now, this is simply a refactoring. We will make use of this more in a
subsequent commit in which we use `matchUpSigWithDecl` to determine the binders
of a promoted class declaration, in which case we will need to be able to tell
invisible binders from visible ones.
…eAbstractions

When promoting a class with a standalone kind signature, it is possible to
ensure that the promoted methods' kind variables are quantified in the same
order as the original method's type variables. This is now possible thanks to
the `TypeAbstractions` language extension, which lets `singletons-th` pick the
exact order of kind variables in the promoted methods' associated type families
using `@` binders. For the full details of how this is accomplished, see the
expanded `Note [Promoted class methods and kind variable ordering]` in
`D.S.TH.Promote` (specifically, `Case 1: Parent class with standalone kind
signature`).

As it turns out, many of the steps we need to perform to achieve this are the
same steps that the `singDataSAK` function performs when producing a standalone
kind signature for a singled `data` declaration. As such, I factored out the
common bits into a helper function called `matchUpSAKWithDecl` in
`D.S.TH.Util`.

Fixes #589.
@RyanGlScott RyanGlScott force-pushed the T589-class-method-type-variable-order branch from 33e351e to 8705840 Compare June 6, 2024 11:41
@RyanGlScott RyanGlScott merged commit 0f4bfc3 into master Jun 6, 2024
24 checks passed
@RyanGlScott RyanGlScott deleted the T589-class-method-type-variable-order branch June 6, 2024 11:47
RyanGlScott added a commit that referenced this pull request Jun 16, 2024
The changes in #596 cause `singletons-th` to use `TypeAbstractions` to
explicitly quantify the kind variables of promoted class methods whenever the
parent class has a standalone kind signature, thereby ensuring that the order
of kind variables matches the order in which the user wrote them. At least,
that was the intention. Unfortunately, as #605 reveals, this approach sometimes
causes `singletons-th` to generate ill-kinded code for promoted class methods,
and there isn't an obvious way to work around this limitation.

As such, this patch reverts the `TypeAbstractions`-related changes from #596.
Once again, `singletons-th` now does not make any guarantees about the order of
kind variables for promoted class methods or their defunctionalization symbols.

On the other hand, this patch _does_ keep the changes from #596 that cause
`singletons-th` to propagate kind information from the parent class's
standalone kind signature through to the promoted class methods'
defunctionalization symbols, as this feature is useful independent of the
`TypeAbstractions`-related changes. I have taken the opportunity to document
why we do this in the new `Note [Propagating kind information from class
standalone kind signatures]` in `D.S.TH.Promote`.

I've removed the `T589` test case, as the functionality it was testing no
longer exists after reverting the `TypeAbstractions`-related changes. I have
also added a new `T605` test case that ensures that the regression from #605
stays fixed. In a subsequent commit, I will add another test case that
demonstrates that the kind propagation works as intended.

Fixes #605.
RyanGlScott added a commit that referenced this pull request Jun 18, 2024
The changes in #596 cause `singletons-th` to use `TypeAbstractions` to
explicitly quantify the kind variables of promoted class methods whenever the
parent class has a standalone kind signature, thereby ensuring that the order
of kind variables matches the order in which the user wrote them. At least,
that was the intention. Unfortunately, as #605 reveals, this approach sometimes
causes `singletons-th` to generate ill-kinded code for promoted class methods,
and there isn't an obvious way to work around this limitation.

As such, this patch reverts the `TypeAbstractions`-related changes from #596.
Once again, `singletons-th` now does not make any guarantees about the order of
kind variables for promoted class methods or their defunctionalization symbols.

On the other hand, this patch _does_ keep the changes from #596 that cause
`singletons-th` to propagate kind information from the parent class's
standalone kind signature through to the promoted class methods'
defunctionalization symbols, as this feature is useful independent of the
`TypeAbstractions`-related changes. I have taken the opportunity to document
why we do this in the new `Note [Propagating kind information from class
standalone kind signatures]` in `D.S.TH.Promote`.

I've removed the `T589` test case, as the functionality it was testing no
longer exists after reverting the `TypeAbstractions`-related changes. I have
also added a new `T605` test case that ensures that the regression from #605
stays fixed. In a subsequent commit, I will add another test case that
demonstrates that the kind propagation works as intended.

Fixes #605.
RyanGlScott added a commit that referenced this pull request Jun 30, 2024
When promoting a class or instance method, we generate a "helper" type family
definition that contains the actual implementation of the class or instance
method. Prior to this patch, it was possible that the kind of the helper type
family could be more polymorphic than desired. For instance, `singletons-th`
would promote this:

```hs
$(promote [d|
  type MyApplicative :: (Type -> Type) -> Constraint
  class Functor f => MyApplicative f where
    ap :: f (a -> b) -> f a -> f b

    rightSparrow :: f a -> f b -> f b
    rightSparrow x y = ap (id <$ x) y
  |])
```

To this:

```hs
type PMyApplicative :: (Type -> Type) -> Constraint
class PMyApplicative f where
  type Ap (x :: f (a ~> b)) (y :: f a) :: f b
  type RightSparrow (x :: f a) (y :: f b) :: f b
  type RightSparrow x y = RightSparrowDefault x y

-- The helper type family
type RightSparrowDefault :: forall f a b. f a -> f b -> f b
type family RightSparrowDefault x y where
  RightSparrowDefault x y = Ap (IdSym0 <$ x) y
```

Note that GHC would generalize the standalone kind signature of
`RightSparrowDefault` to:

```hs
type RightSparrowDefault :: forall {k} (f :: k -> Type) (a :: k) (b :: k).
                            f a -> f b -> f b
```

This is more general than intended, as we want `f` to be of kind `Type -> Type`
instead. After all, we said as much in the standalone kind signature for
`MyApplicative`! This excessive polymorphism doesn't actively cause problems in
today's GHC, but they will cause problems in a future version of GHC that
implements [GHC#23515](https://gitlab.haskell.org/ghc/ghc/-/issues/23515).
(See #601.)

This patch resolves the issue by propagating kind information from the class's
standalone kind signature (or, in the case of instance declarations, the
instance head) to the helper type family declarations. After this patch, we now
generate the following kind for `RightSparrowDefault` (as verified by the new
`T601a` test case):

```hs
type RightSparrowDefault :: forall (f :: Type -> Type) a b.
                            f a -> f b -> f b
```

This piggybacks on machinery that was added in #596 to do most of the heavy
lifting.

Resolves the "Overly polymorphic promoted class defaults" section of #601.
RyanGlScott added a commit that referenced this pull request Jun 30, 2024
When promoting a class or instance method, we generate a "helper" type family
definition that contains the actual implementation of the class or instance
method. Prior to this patch, it was possible that the kind of the helper type
family could be more polymorphic than desired. For instance, `singletons-th`
would promote this:

```hs
$(promote [d|
  type MyApplicative :: (Type -> Type) -> Constraint
  class Functor f => MyApplicative f where
    ap :: f (a -> b) -> f a -> f b

    rightSparrow :: f a -> f b -> f b
    rightSparrow x y = ap (id <$ x) y
  |])
```

To this:

```hs
type PMyApplicative :: (Type -> Type) -> Constraint
class PMyApplicative f where
  type Ap (x :: f (a ~> b)) (y :: f a) :: f b
  type RightSparrow (x :: f a) (y :: f b) :: f b
  type RightSparrow x y = RightSparrowDefault x y

-- The helper type family
type RightSparrowDefault :: forall f a b. f a -> f b -> f b
type family RightSparrowDefault x y where
  RightSparrowDefault x y = Ap (IdSym0 <$ x) y
```

Note that GHC would generalize the standalone kind signature of
`RightSparrowDefault` to:

```hs
type RightSparrowDefault :: forall {k} (f :: k -> Type) (a :: k) (b :: k).
                            f a -> f b -> f b
```

This is more general than intended, as we want `f` to be of kind `Type -> Type`
instead. After all, we said as much in the standalone kind signature for
`MyApplicative`! This excessive polymorphism doesn't actively cause problems in
today's GHC, but they will cause problems in a future version of GHC that
implements [GHC#23515](https://gitlab.haskell.org/ghc/ghc/-/issues/23515).
(See #601.)

This patch resolves the issue by propagating kind information from the class's
standalone kind signature (or, in the case of instance declarations, the
instance head) to the helper type family declarations. After this patch, we now
generate the following kind for `RightSparrowDefault` (as verified by the new
`T601a` test case):

```hs
type RightSparrowDefault :: forall (f :: Type -> Type) a b.
                            f a -> f b -> f b
```

This piggybacks on machinery that was added in #596 to do most of the heavy
lifting.

Resolves the "Overly polymorphic promoted class defaults" section of #601.
RyanGlScott added a commit that referenced this pull request Jul 1, 2024
When promoting a class or instance method, we generate a "helper" type family
definition that contains the actual implementation of the class or instance
method. Prior to this patch, it was possible that the kind of the helper type
family could be more polymorphic than desired. For instance, `singletons-th`
would promote this:

```hs
$(promote [d|
  type MyApplicative :: (Type -> Type) -> Constraint
  class Functor f => MyApplicative f where
    ap :: f (a -> b) -> f a -> f b

    rightSparrow :: f a -> f b -> f b
    rightSparrow x y = ap (id <$ x) y
  |])
```

To this:

```hs
type PMyApplicative :: (Type -> Type) -> Constraint
class PMyApplicative f where
  type Ap (x :: f (a ~> b)) (y :: f a) :: f b
  type RightSparrow (x :: f a) (y :: f b) :: f b
  type RightSparrow x y = RightSparrowDefault x y

-- The helper type family
type RightSparrowDefault :: forall f a b. f a -> f b -> f b
type family RightSparrowDefault x y where
  RightSparrowDefault x y = Ap (IdSym0 <$ x) y
```

Note that GHC would generalize the standalone kind signature of
`RightSparrowDefault` to:

```hs
type RightSparrowDefault :: forall {k} (f :: k -> Type) (a :: k) (b :: k).
                            f a -> f b -> f b
```

This is more general than intended, as we want `f` to be of kind `Type -> Type`
instead. After all, we said as much in the standalone kind signature for
`MyApplicative`! This excessive polymorphism doesn't actively cause problems in
today's GHC, but they will cause problems in a future version of GHC that
implements [GHC#23515](https://gitlab.haskell.org/ghc/ghc/-/issues/23515).
(See #601.)

This patch resolves the issue by propagating kind information from the class's
standalone kind signature (or, in the case of instance declarations, the
instance head) to the helper type family declarations. After this patch, we now
generate the following kind for `RightSparrowDefault` (as verified by the new
`T601a` test case):

```hs
type RightSparrowDefault :: forall (f :: Type -> Type) a b.
                            f a -> f b -> f b
```

This piggybacks on machinery that was added in #596 to do most of the heavy
lifting.

Resolves the "Overly polymorphic promoted class defaults" section of #601.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
1 participant