You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
In type family and data family instances, the instantiation is fully determined by the left hand side, without looking at the right hand side.
A reasonable change, but one that will nevertheless cause several parts of singletons and singletons-base to fail to compile. This issue aims to identify all of these sources of breakage and how to fix them.
(There is a prototype implementation of this GHC proposal in GHC!12776, which is what I used to discover these breakages.)
There are places in singletons and singletons-base that declare Sing instances without specifying what type the Sing instance is for on the left-hand side, e.g.,
typeinstanceSing=SNat
This will no longer work after GHC#23515 is implemented. Instead, we must write:
typeinstanceSing@Nat=SNat
The following places in the code will need to be updated:
$ ~/Software/ghc3/_build/stage1/bin/ghc Foo.hs
[1 of 1] Compiling Foo ( Foo.hs, Foo.o )
Foo.hs:50:58: error: [GHC-25897]
• Couldn't match kind ‘b1’ with ‘b2’
Expected kind ‘b2 ~> (a3 ~> b2)’,
but ‘f’ has kind ‘b1 ~> (a1 ~> b1)’
‘b1’ is a rigid type variable bound by
a family instance declaration
at Foo.hs:50:15-19
‘b2’ is a rigid type variable bound by
a family instance declaration
at Foo.hs:50:21-42
• In the third argument of ‘LgoSym5’, namely ‘f’
In the type instance declaration for ‘Apply’
|
50 | type instance Apply (LgoSym4 a b f z0) xs0 = LgoSym5 a b f z0 xs0
| ^
The reason this happens is because the kinds of some of these defunctionalization symbols are more polymorphic than we want. For instance, compare the kinds of LgoSym4 and LgoSym5:
Note that the kind of LgoSym5 uses visible foralls, whereas the kind of LgoSym4 does not. This actually matters in practice, because when you write this:
typeinstanceApply (LgoSym4abfz0) xs0=...
If we do nothing but look at the left-hand side of the type family instance, we conclude that:
Note that the type variables in @(b1 ~> [a1] ~> b1) are not the same as in (LgoSym4 a b ...), because nothing in the kind of LgoSym4 relates the two.
Now GHC proceeds to kind-check the right-hand-side against kind b1 ~> [a1] ~> b1:
LgoSym5 a b f z0 xs0 ::b1~> [a1] ~>b1
But this doesn't work, because LgoSym5 a b f z0 xs0 has kind b ~> [a] ~> b, not b1 ~> [a1] ~> b1! If we were allowed to unify a/a1 and b/b1, then this would kind-check, but this is not possible due to the changes brought on by GHC#23515.
I can see two possible ways to fix this:
If we wrote the defunctionalization symbols like this instead:
Then the kinds of all defunctionalization symbols use visible foralls, thereby giving GHC enough information to kind-check this program even with the changes brought on by GHC#23515:
Note that the underlying issue still applies regardless of the changes above, however—you'll still need to provide standalone kind signatures in places that didn't require them before.
Consider this program:
$(promote [d|
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
|])
We have not given MyApplicative a standalone kind signature, but the intent is that f is inferred to be of kind Type -> Type. However, consider what happens when MyApplicative is promoted:
{-# LANGUAGE GHC2024 #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeAbstractions #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
moduleFoowhereimportData.KindtypeTyFun::Type->Type->TypedataTyFunabtype (~>) ::Type->Type->Typetypea~>b=TyFunab->Typeinfixr0~>typeApply:: (a~>b) ->a->btypefamilyApplyfxtypeId::a->atypefamilyIdxwhereIdx=xtypeIdSym0::a~>adataIdSym0ztypeinstanceApplyIdSym0x=IdxclassPFunctorfwheretype (x::a) <$ (y::fb) ::faclassPMyApplicativefwheretypeAp (x::f (a~>b)) (y::fa) ::fbtypeRightSparrow (x::fa) (y::fb) ::fbtypeRightSparrowxy=RightSparrowDefaultxytypeRightSparrowDefault::fa->fb->fbtypefamilyRightSparrowDefaultxywhereRightSparrowDefaultxy=Ap (IdSym0<$x) y
There's something suspicious about RightSparrowDefault. It has the standalone kind signature f a -> f b -> f b, and GHC will kind-generalize this to forall {k} (f :: k -> Type) (a :: k) (b :: k). f a -> f b -> f b. The actual body of RightSparrowDefault, however, requires k to be Type. GHC does not like this after the changes brought on by GHC#23515, and it will reject it:
$ ~/Software/ghc3/_build/stage1/bin/ghc Foo.hs
[1 of 1] Compiling Foo ( Foo.hs, Foo.o )
Foo.hs:38:43: error: [GHC-25897]
• Couldn't match kind ‘k’ with ‘*’
When matching kinds
a :: k
b0 :: *
Expected kind ‘f0 b0’, but ‘x’ has kind ‘f a’
‘k’ is a rigid type variable bound by
a family instance declaration
at Foo.hs:38:3-46
• In the second argument of ‘(<$)’, namely ‘x’
In the first argument of ‘Ap’, namely ‘(IdSym0 <$ x)’
In the type family declaration for ‘RightSparrowDefault’
|
38 | RightSparrowDefault x y = Ap (IdSym0 <$ x) y
| ^
It's tempting to try to repair the issue by removing the standalone kind signature:
typefamilyRightSparrowDefault (x::fa) (y::fb) ::fbwhereRightSparrowDefaultxy=Ap (IdSym0<$x) y
But GHC won't accept that, regardless of whether you have the changes from GHC#23515 or not:
$ ~/Software/ghc3/_build/stage1/bin/ghc Foo.hs
[1 of 1] Compiling Foo ( Foo.hs, Foo.o )
Foo.hs:32:12: error: [GHC-17370]
• Different names for the same type variable: ‘a’ and ‘b’
• In the class declaration for ‘PMyApplicative’
|
32 | type Ap (x :: f (a ~> b)) (y :: f a) :: f b
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
As such, we're stuck between a rock and a hard place.
Unfortunately, I don't know if singletons-th will be able to promote classes like MyApplicative anymore—at least, not without making some minor edits to clarify what the kind of f is. My vision is being able to write this instead:
$(promote [d|
type MyApplicative :: (Type -> Type) -> Constraint
class Functor f => MyApplicative f where
-- Or, alternatively,
-- class Functor f => MyApplicative (f :: Type) where
...
|])
And then singletons-th would generate this default instead:
typeRightSparrowDefault::forall (f::Type->Type) ab.fa->fb->fbtypefamilyRightSparrowDefaultxywhereRightSparrowDefaultxy=Ap (IdSym0<$x) y
And then everything would be fine and dandy. Currently, singletons-th does not do this, but it could with a bit of additional work to make the (Type -> Type) kind from the class flow down through to the promoted class method default.
EDIT: I believe all known examples of this issue in singletons-base have been worked around in #607 and #611. The underlying issue still applies, however.
Just as class defaults can be overly polymorphic, so too can instance methods. Consider this example:
This is too general for our needs, as calling Fmap requires that both f and g be of kind Type -> Type:
$ ~/Software/ghc3/_build/stage1/bin/ghc Foo.hs
[1 of 1] Compiling Foo ( Foo.hs, Foo.o )
Foo.hs:37:60: error: [GHC-25897]
• Couldn't match kind ‘k’ with ‘*’
When matching kinds
g :: * -> k
g0 :: * -> *
Expected kind ‘f0 (g0 a)’, but ‘x’ has kind ‘f (g a)’
‘k’ is a rigid type variable bound by
a family instance declaration
at Foo.hs:37:3-61
• In the second argument of ‘Fmap’, namely ‘x’
In the first argument of ‘'Compose’, namely ‘(Fmap (FmapSym1 h) x)’
In the type family declaration for ‘FmapCompose’
|
37 | FmapCompose h ('Compose x) = 'Compose (Fmap (FmapSym1 h) x)
| ^
GHC will kind-generalize the instance PFunctor (Compose f g) declaration to:
$ ~/Software/ghc3/_build/stage1/bin/ghc Foo.hs
[1 of 1] Compiling Foo ( Foo.hs, Foo.o )
Foo.hs:33:33: error: [GHC-25897]
• Couldn't match kind ‘k’ with ‘*’
Expected kind ‘Compose @{*} @{*} f g a’,
but ‘x’ has kind ‘Compose @{k} @{*} f g a’
‘k’ is a rigid type variable bound by
a family instance declaration
at Foo.hs:(32,1)-(33,33)
• In the second argument of ‘FmapCompose’, namely ‘x’
In the type instance declaration for ‘Fmap’
In the instance declaration for ‘PFunctor (Compose f g)’
|
33 | type Fmap h x = FmapCompose h x
| ^
Again, I think users will need some kind of manual edits in order to make this sort of code work. A workaround exists in today's singletons-th which works well enough:
$(promote [d|
-- NB: Note the explicit `f :: Type -> Type` kind signature
instance (Functor f, Functor g) => Functor (Compose (f :: Type -> Type) g) where
fmap h (Compose x) = Compose (fmap (fmap h) x)
|])
This will make singletons-th generate code that looks like this:
{-# LANGUAGE GHC2024 #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeAbstractions #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
moduleFoowhereimportData.Functor.ComposeimportData.KindtypeTyFun::Type->Type->TypedataTyFunabtype (~>) ::Type->Type->Typetypea~>b=TyFunab->Typeinfixr0~>typeApply:: (a~>b) ->a->btypefamilyApplyfxclassPFunctorfwheretypeFmap (h::a~>b) (x::fa) ::fbtypeFmapSym0:: (a~>b) ~>fa~>fbdataFmapSym0ztypeinstanceApplyFmapSym0h=FmapSym1htypeFmapSym1:: (a~>b) ->fa~>fbdataFmapSym1gztypeinstanceApply (FmapSym1h) x=FmaphxinstancePFunctor (Compose (f::Type->Type) g) wheretypeFmaphx=FmapComposehxtypeFmapCompose::forall (f::Type->Type) gab.
(a~>b) ->Composefga->ComposefgbtypefamilyFmapComposegcwhereFmapComposeh ('Compose x) = 'Compose (Fmap (FmapSym1h) x)
$(promote [d|
type P :: forall k -> k -> Type
type P k (a :: k) = Proxy a
|])
singletons-th will generate this code:
{-# LANGUAGE GHC2024 #-}
{-# LANGUAGE NoStarIsType #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeAbstractions #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
moduleFoowhereimportData.KindimportData.ProxytypeTyFun::Type->Type->TypedataTyFunabtype (~>) ::Type->Type->Typetypea~>b=TyFunab->Typeinfixr0~>typeApply:: (a~>b) ->a->btypefamilyApplyfxtypeSameKind::k->k->ConstrainttypeSameKindab= (()::Constraint)
typeP::forallk->k->TypetypePk (a::k) =ProxyadataPSym0zwherePSym0KindInference::SameKind (ApplyPSym0arg) (PSym1arg) =>PSym0ktypeinstanceApplyPSym0k=PSym1kdataPSym1k::k~>TypewherePSym1KindInference::SameKind (Apply (PSym1k) arg) (PSym2karg) =>PSym1katypeinstanceApply (PSym1k) a=PSym2katypefamilyPSym2k (a::k) ::TypewherePSym2ka=Pka(#overly-polymorphic-lambda-lifting-part-1)
GHC will now reject this with:
$ ~/Software/ghc3/_build/stage1/bin/ghc Foo.hs
[1 of 1] Compiling Foo ( Foo.hs, Foo.o )
Foo.hs:31:31: error: [GHC-25897]
• Couldn't match kind ‘k’ with ‘arg’
Expected kind ‘TyFun arg Type -> Type’,
but ‘PSym1 k’ has kind ‘TyFun k Type -> Type’
‘k’ is a rigid type variable bound by
a family instance declaration
at Foo.hs:31:21-27
‘arg’ is a rigid type variable bound by
a family instance declaration
at Foo.hs:31:15-19
• In the type instance declaration for ‘Apply’
|
31 | type instance Apply PSym0 k = PSym1 k
| ^^^^^^^
If this feels familiar, it's because we've already seen this exact same scenario play out before in the Overly polymorphic lambda-lifting, part 1 section. In particular, we have defunctionalization symbols with the following kinds:
PSym0::Type~>k~>TypePSym1::forallk->k~>Type
Note that the kind of PSym1 uses a visible forall, whereas the kind of PSym0 does not. Therefore, when GHC kind-checks the left-hand side of this type family instance:
$(promote [d|
nub :: forall a. (Eq a) => [a] -> [a]
nub l = nub' l []
where
nub' :: [a] -> [a] -> [a]
nub' [] _ = []
nub' (x:xs) ls = if x `elem` ls then nub' xs ls else x : nub' xs (x:ls)
|])
singletons-th will generate (roughly) the following code when promoting nub:
$ ~/Software/ghc3/_build/stage1/bin/ghc Foo.hs
[1 of 1] Compiling Foo ( Foo.hs, Foo.o )
Foo.hs:20:37: error: [GHC-25897]
• Couldn't match kind ‘a1’ with ‘a2’
Expected kind ‘[a2]’, but ‘xs’ has kind ‘[a1]’
‘a1’ is a rigid type variable bound by
a family instance declaration
at Foo.hs:20:3-41
‘a2’ is a rigid type variable bound by
a family instance declaration
at Foo.hs:20:8-23
• In the third argument of ‘Nub'’, namely ‘xs’
In the type family declaration for ‘Case’
|
20 | Case a x xs ls l True = Nub' a l xs ls
| ^^
Foo.hs:21:41: error: [GHC-25897]
• Couldn't match kind ‘a1’ with ‘a2’
Expected kind ‘[a2]’, but ‘xs’ has kind ‘[a1]’
‘a1’ is a rigid type variable bound by
a family instance declaration
at Foo.hs:21:3-49
‘a2’ is a rigid type variable bound by
a family instance declaration
at Foo.hs:21:8-24
• In the third argument of ‘Nub'’, namely ‘xs’
In the second argument of ‘(:)’, namely ‘Nub' a l xs (x : ls)’
In the type family declaration for ‘Case’
|
21 | Case a x xs ls l False = x : Nub' a l xs (x:ls)
| ^^
The problem is that Case's kind is too polymorphic:
Case::Type->a-> [a] -> [a] ->k->Bool-> [a]
Rather than something like:
Case::foralla->a-> [a] -> [a] -> [a] ->Bool-> [a]
However, we can do better here. Note that none of the arguments to Case have any kind signatures whatsoever:
typefamilyCaseaxxslsltwhere...
This is silly, because we can determine the kinds of several of these arguments (e.g., ls and l) by looking at the syntax of nub. And indeed, if we sprinkle even a couple of these kind signatures into the definition of Case:
typefamilyCaseaxxs (ls:: [a]) (l:: [a]) twhere...
Then GHC accepts the program once more. It should be possible to generate these kind signatures during lambda lifting.
Parts of singletons-base which are affected by this issue are:
Here is a problematic example, boiled down from the Singletons/T296.hs test case:
$(promote [d|
n :: forall a. Maybe a
n = let z = let x :: Maybe a
x = Nothing in x
in z
|])
This translates to the following code:
{-# LANGUAGE GHC2024 #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeAbstractions #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
moduleBugwhereimportData.KindtypefamilyLetXa::MaybeawhereLetXa= 'Nothing
typefamilyLetZawhereLetZa=LetXatypeN::foralla.MaybeatypefamilyN@a::MaybeawhereN@a=LetZa
LetZ is problematic. GHC 9.10.1 accepts it, but gives it a counterintuitive kind:
λ> :info LetZ
type LetZ :: forall {a}. Type -> Maybe a
type family LetZ @{a} a1 where
forall a. LetZ @{a} a = LetX a
Rather than giving it the kind forall a -> Maybe a, it's given the kind Type -> Maybe a, and the type family equation for LetZ matches on the invisible kind argument (LetZ @{a} a = ...). A more recent GHC will reject this with:
$ ~/Software/ghc3/_build/stage1/bin/ghc Bug.hs
[1 of 1] Compiling Bug ( Bug.hs, Bug.o )
Bug.hs:14:12: error: [GHC-25897]
• Couldn't match kind ‘a2’ with ‘a1’
Expected kind ‘Maybe a1’, but ‘LetX a’ has kind ‘Maybe a2’
‘a2’ is a rigid type variable bound by
a family instance declaration
at Bug.hs:14:8
‘a1’ is a rigid type variable bound by
a family instance declaration
at Bug.hs:14:3-17
• In the type family declaration for ‘LetZ’
|
14 | LetZ a = LetX a
| ^^^^^^
Some singletons-base test suite failures that have similar root causes:
Singletons/CaseExpressions.hs:0:0: error: [GHC-25897]
• Expected kind ‘a0’, but ‘y’ has kind ‘a’
‘a0’ is a rigid type variable bound by
a family instance declaration
at Singletons/CaseExpressions.hs:(0,0)-(0,0)
‘a’ is a rigid type variable bound by
a family instance declaration
at Singletons/CaseExpressions.hs:(0,0)-(0,0)
• In the second argument of ‘Let0123456789876543210ZSym0’, namely
‘y’
In the type family declaration for ‘Case_0123456789876543210’
|
8 | $(singletons [d|
| ^^^^^^^^^^^^^^^...
Singletons/T183.hs:0:0: error: [GHC-25897]
• Expected kind ‘a0’, but ‘wild_0123456789876543210’ has kind ‘a’
‘a0’ is a rigid type variable bound by
a family instance declaration
at Singletons/T183.hs:(0,0)-(0,0)
‘a’ is a rigid type variable bound by
a family instance declaration
at Singletons/T183.hs:(0,0)-(0,0)
• In the second argument of ‘Apply’, namely
‘(wild_0123456789876543210 :: a)’
In the type family declaration for ‘Let0123456789876543210X’
|
6 | $(singletons [d|
| ^^^^^^^^^^^^^^^...
Singletons/T296.hs:0:0: error: [GHC-25897]
• Couldn't match kind ‘a0’ with ‘a’
Expected kind ‘MyProxy a’,
but ‘Let0123456789876543210ZSym0 a’ has kind ‘MyProxy a0’
‘a0’ is a rigid type variable bound by
a family instance declaration
at Singletons/T296.hs:(0,0)-(0,0)
‘a’ is a rigid type variable bound by
a family instance declaration
at Singletons/T296.hs:(0,0)-(0,0)
• In the type family declaration for ‘Let0123456789876543210X’
|
6 | $(singletons [d|
| ^^^^^^^^^^^^^^^...
Singletons/T433.hs:0:0: error: [GHC-25897]
• Expected kind ‘b2’, but ‘x’ has kind ‘b1’
‘b1’ is a rigid type variable bound by
a family instance declaration
at Singletons/T433.hs:(0,0)-(0,0)
‘b2’ is a rigid type variable bound by
a family instance declaration
at Singletons/T433.hs:(0,0)-(0,0)
• In the type family declaration for ‘Let0123456789876543210G’
|
7 | $(promote [d|
| ^^^^^^^^^^^^...
Singletons/T581.hs:0:0: error: [GHC-25897]
• Expected kind ‘a0’, but ‘xx’ has kind ‘a’
‘a0’ is a rigid type variable bound by
a family instance declaration
at Singletons/T581.hs:(0,0)-(0,0)
‘a’ is a rigid type variable bound by
a family instance declaration
at Singletons/T581.hs:(0,0)-(0,0)
• In the type family declaration for ‘Lambda_0123456789876543210’
|
6 | $(singletons
| ^^^^^^^^^^^...
Truth be told, I'm not quite sure what to do about these sorts of examples. The only way I can imagine fixing this examples is by either rewriting the code or by sprinkling type annotations in random parts of the code. You might think that the takeaway here is "local bindings without top-level type signatures are bad", but that's not quite true, since GHC will continue to accept examples like this one:
$(promote [d|
n :: forall a. Maybe a
n = let z = Nothing
in z
|])
Sadly, we may just have to accept that GHC's kind inference just isn't up to the task in all cases and advise users to omit type signatures at their peril.
GHC#23515 tracks implementing this portion of GHC proposal #425 (Invisible binders in type declarations):
A reasonable change, but one that will nevertheless cause several parts of
singletonsandsingletons-baseto fail to compile. This issue aims to identify all of these sources of breakage and how to fix them.(There is a prototype implementation of this GHC proposal in GHC!12776, which is what I used to discover these breakages.)
Table of Contents
Singinstances without explicit left-hand sidesEDIT: This was fixed by #602.
There are places in
singletonsandsingletons-basethat declareSinginstances without specifying what type theSinginstance is for on the left-hand side, e.g.,This will no longer work after GHC#23515 is implemented. Instead, we must write:
The following places in the code will need to be updated:
singletons/singletons/src/Data/Singletons.hs
Line 408 in 7e5c92d
singletons/singletons/src/Data/Singletons.hs
Line 733 in 7e5c92d
singletons/singletons/src/Data/Singletons/Sigma.hs
Line 102 in 7e5c92d
singletons/singletons/tests/ByHand.hs
Line 101 in 7e5c92d
singletons/singletons/tests/ByHand.hs
Line 155 in 7e5c92d
singletons/singletons/tests/ByHand.hs
Line 195 in 7e5c92d
singletons/singletons/tests/ByHand.hs
Line 245 in 7e5c92d
singletons/singletons/tests/ByHand.hs
Line 318 in 7e5c92d
singletons/singletons/tests/ByHand.hs
Line 364 in 7e5c92d
singletons/singletons/tests/ByHand.hs
Line 396 in 7e5c92d
singletons/singletons/tests/ByHand.hs
Line 423 in 7e5c92d
singletons/singletons/tests/ByHand.hs
Line 1042 in 7e5c92d
singletons/singletons/tests/ByHand2.hs
Line 30 in 7e5c92d
singletons/singletons/tests/ByHand2.hs
Line 43 in 7e5c92d
singletons/singletons/tests/ByHand2.hs
Line 57 in 7e5c92d
singletons/singletons-th/src/Data/Singletons/TH/CustomStar.hs
Line 69 in 7e5c92d
singletons/singletons-base/src/Data/Foldable/Singletons.hs
Line 130 in 7e5c92d
singletons/singletons-base/src/Data/Functor/Compose/Singletons.hs
Line 56 in 7e5c92d
singletons/singletons-base/src/Data/Functor/Const/Singletons.hs
Line 78 in 7e5c92d
singletons/singletons-base/src/Data/Functor/Product/Singletons.hs
Line 60 in 7e5c92d
singletons/singletons-base/src/Data/Functor/Sum/Singletons.hs
Line 56 in 7e5c92d
singletons/singletons-base/src/Data/Proxy/Singletons.hs
Line 63 in 7e5c92d
singletons/singletons-base/src/Data/Traversable/Singletons.hs
Line 67 in 7e5c92d
singletons/singletons-base/src/Data/Traversable/Singletons.hs
Line 77 in 7e5c92d
singletons/singletons-base/src/Data/Singletons/Base/TypeError.hs
Line 85 in 7e5c92d
singletons/singletons-base/src/GHC/TypeLits/Singletons/Internal.hs
Line 69 in 7e5c92d
Overly polymorphic lambda-lifting, part 1
EDIT: This should be fixed as of #593.
This code will fail to compile after GHC#23515 is implemented:
singletons/singletons-base/src/Data/Singletons/Base/Instances.hs
Lines 34 to 41 in 7e5c92d
This is because we will promote
foldlto code that looks something like this:{-# LANGUAGE GHC2024 #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeAbstractions #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE UndecidableInstances #-} module Foo where import Data.Kind type TyFun :: Type -> Type -> Type data TyFun a b type (~>) :: Type -> Type -> Type type a ~> b = TyFun a b -> Type infixr 0 ~> type Apply :: (a ~> b) -> a -> b type family Apply f x type SameKind :: k -> k -> Constraint type SameKind a b = (() :: Constraint) type family Lgo a b f z0 xs0 (z :: b) (ls :: [a]) :: b where Lgo a b f z0 xs0 z '[] = z Lgo a b f z0 xs0 z (x:xs) = Lgo a b f z0 xs0 (f `Apply` z `Apply` x) xs data LgoSym0 tf where LgoSym0KindInference :: SameKind (Apply LgoSym0 arg) (LgoSym1 arg) => LgoSym0 a type instance Apply LgoSym0 a = LgoSym1 a data LgoSym1 a tf where LgoSym1KindInference :: SameKind (Apply (LgoSym1 a) arg) (LgoSym2 a arg) => LgoSym1 a b type instance Apply (LgoSym1 a) b = LgoSym2 a b data LgoSym2 a b tf where LgoSym2KindInference :: SameKind (Apply (LgoSym2 a b) arg) (LgoSym3 a b arg) => LgoSym2 a b f type instance Apply (LgoSym2 a b) f = LgoSym3 a b f data LgoSym3 a b f tf where LgoSym3KindInference :: SameKind (Apply (LgoSym3 a b f) arg) (LgoSym4 a b f arg) => LgoSym3 a b f z0 type instance Apply (LgoSym3 a b f) z0 = LgoSym4 a b f z0 data LgoSym4 a b f z0 tf where LgoSym4KindInference :: SameKind (Apply (LgoSym4 a b f z0) arg) (LgoSym5 a b f z0 arg) => LgoSym4 a b f z0 xs0 type instance Apply (LgoSym4 a b f z0) xs0 = LgoSym5 a b f z0 xs0 data LgoSym5 a b f z0 xs0 :: b ~> [a] ~> b where LgoSym5KindInference :: SameKind (Apply (LgoSym5 a b f z0 xs0) arg) (LgoSym6 a b f z0 xs0 arg) => LgoSym5 a b f z0 xs0 z type instance Apply (LgoSym5 a b f z0 xs0) z = LgoSym6 a b f z0 xs0 z data LgoSym6 a b f z0 xs0 (z :: b) :: [a] ~> b where LgoSym6KindInference :: SameKind (Apply (LgoSym6 a b f z0 xs0 z) arg) (LgoSym7 a b f z0 xs0 z arg) => LgoSym6 a b f z0 xs0 z ls type instance Apply (LgoSym6 a b f z0 xs0 z) ls = LgoSym7 a b f z0 xs0 z ls type family LgoSym7 a b f z0 xs0 (z :: b) (ls :: [a]) :: b where LgoSym7 a b f z0 xs0 z ls = Lgo a b f z0 xs0 z lsAnd this will fail with:
The reason this happens is because the kinds of some of these defunctionalization symbols are more polymorphic than we want. For instance, compare the kinds of
LgoSym4andLgoSym5:Note that the kind of
LgoSym5uses visibleforalls, whereas the kind ofLgoSym4does not. This actually matters in practice, because when you write this:If we do nothing but look at the left-hand side of the type family instance, we conclude that:
Note that the type variables in
@(b1 ~> [a1] ~> b1)are not the same as in(LgoSym4 a b ...), because nothing in the kind ofLgoSym4relates the two.Now GHC proceeds to kind-check the right-hand-side against kind
b1 ~> [a1] ~> b1:But this doesn't work, because
LgoSym5 a b f z0 xs0has kindb ~> [a] ~> b, notb1 ~> [a1] ~> b1! If we were allowed to unifya/a1andb/b1, then this would kind-check, but this is not possible due to the changes brought on by GHC#23515.I can see two possible ways to fix this:
If we wrote the defunctionalization symbols like this instead:
{-# LANGUAGE GHC2024 #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeAbstractions #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE UndecidableInstances #-} module Foo where import Data.Kind type TyFun :: Type -> Type -> Type data TyFun a b type (~>) :: Type -> Type -> Type type a ~> b = TyFun a b -> Type infixr 0 ~> type Apply :: (a ~> b) -> a -> b type family Apply f x type SameKind :: k -> k -> Constraint type SameKind a b = (() :: Constraint) type family Lgo a b f z0 xs0 (z :: b) (ls :: [a]) :: b where Lgo a b f z0 xs0 z '[] = z Lgo a b f z0 xs0 z (x:xs) = Lgo a b f z0 xs0 (f `Apply` z `Apply` x) xs data LgoSym0 a b f z0 xs0 :: b ~> [a] ~> b where LgoSym0KindInference :: SameKind (Apply (LgoSym0 a b f z0 xs0) arg) (LgoSym1 a b f z0 xs0 arg) => LgoSym0 a b f z0 xs0 z type instance Apply (LgoSym0 a b f z0 xs0) z = LgoSym1 a b f z0 xs0 z data LgoSym1 a b f z0 xs0 (z :: b) :: [a] ~> b where LgoSym1KindInference :: SameKind (Apply (LgoSym1 a b f z0 xs0 z) arg) (LgoSym2 a b f z0 xs0 z arg) => LgoSym1 a b f z0 xs0 z ls type instance Apply (LgoSym1 a b f z0 xs0 z) ls = LgoSym2 a b f z0 xs0 z ls type family LgoSym2 a b f z0 xs0 (z :: b) (ls :: [a]) :: b where LgoSym2 a b f z0 xs0 z ls = Lgo a b f z0 xs0 z lsThen the kinds of all defunctionalization symbols use visible
foralls, thereby giving GHC enough information to kind-check this program even with the changes brought on by GHC#23515:This is exactly the change proposed in Reduce defunctionalization symbol bloat related to local variables #592. (I think fixing Reduce defunctionalization symbol bloat related to local variables #592 is worth doing independently of this issue, but it's nice to know that it helps here as well).
Generate this code instead:
It should be possible to generate this code without requiring too much cleverness on
singletons-th's end.Later in the Defunctionalizing declarations using visible dependent quantification section, we will see an example where option (1) won't suffice and where option (2) is required. And later in the Overly polymorphic lambda-lifting, part 2 section, we will see an example where neither option (1) nor option (2) are sufficient.
Overly polymorphic promoted class defaults
EDIT: This issue no longer occurs within
singletons-base:asum,msum, andProductinstances explicit kind signatures #611, all examples of this issue that occur insingletons-basenow have the necessary standalone kind signatures to allowsingletons-thto generate more precise code for promoted class defaults.Note that the underlying issue still applies regardless of the changes above, however—you'll still need to provide standalone kind signatures in places that didn't require them before.
Consider this program:
We have not given
MyApplicativea standalone kind signature, but the intent is thatfis inferred to be of kindType -> Type. However, consider what happens whenMyApplicativeis promoted:{-# LANGUAGE GHC2024 #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeAbstractions #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE UndecidableInstances #-} module Foo where import Data.Kind type TyFun :: Type -> Type -> Type data TyFun a b type (~>) :: Type -> Type -> Type type a ~> b = TyFun a b -> Type infixr 0 ~> type Apply :: (a ~> b) -> a -> b type family Apply f x type Id :: a -> a type family Id x where Id x = x type IdSym0 :: a ~> a data IdSym0 z type instance Apply IdSym0 x = Id x class PFunctor f where type (x :: a) <$ (y :: f b) :: f a 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 type RightSparrowDefault :: f a -> f b -> f b type family RightSparrowDefault x y where RightSparrowDefault x y = Ap (IdSym0 <$ x) yThere's something suspicious about
RightSparrowDefault. It has the standalone kind signaturef a -> f b -> f b, and GHC will kind-generalize this toforall {k} (f :: k -> Type) (a :: k) (b :: k). f a -> f b -> f b. The actual body ofRightSparrowDefault, however, requireskto beType. GHC does not like this after the changes brought on by GHC#23515, and it will reject it:(This is the same issue described in
Note [Fully saturated defunctionalization symbols]).It's tempting to try to repair the issue by removing the standalone kind signature:
But GHC won't accept that, regardless of whether you have the changes from GHC#23515 or not:
As such, we're stuck between a rock and a hard place.
Unfortunately, I don't know if
singletons-thwill be able to promote classes likeMyApplicativeanymore—at least, not without making some minor edits to clarify what the kind offis. My vision is being able to write this instead:And then
singletons-thwould generate this default instead:And then everything would be fine and dandy. Currently,
singletons-thdoes not do this, but it could with a bit of additional work to make the(Type -> Type)kind from the class flow down through to the promoted class method default.Overly polymorphic instance methods
EDIT: I believe all known examples of this issue in
singletons-basehave been worked around in #607 and #611. The underlying issue still applies, however.Just as class defaults can be overly polymorphic, so too can instance methods. Consider this example:
Currently, this instance will be promoted to:
{-# LANGUAGE GHC2024 #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeAbstractions #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE UndecidableInstances #-} module Foo where import Data.Functor.Compose import Data.Kind type TyFun :: Type -> Type -> Type data TyFun a b type (~>) :: Type -> Type -> Type type a ~> b = TyFun a b -> Type infixr 0 ~> type Apply :: (a ~> b) -> a -> b type family Apply f x class PFunctor f where type Fmap (h :: a ~> b) (x :: f a) :: f b type FmapSym0 :: (a ~> b) ~> f a ~> f b data FmapSym0 z type instance Apply FmapSym0 h = FmapSym1 h type FmapSym1 :: (a ~> b) -> f a ~> f b data FmapSym1 g z type instance Apply (FmapSym1 h) x = Fmap h x instance PFunctor (Compose f g) where type Fmap h x = FmapCompose h x type FmapCompose :: (a ~> b) -> Compose f g a -> Compose f g b type family FmapCompose g c where FmapCompose h ('Compose x) = 'Compose (Fmap (FmapSym1 h) x)There are two problems with this code:
When GHC kind-checks
FmapCompose's standalone kind signature, it will kind-generalize it to:This is too general for our needs, as calling
Fmaprequires that bothfandgbe of kindType -> Type:GHC will kind-generalize the
instance PFunctor (Compose f g)declaration to:Which is also too polymorphic:
Again, I think users will need some kind of manual edits in order to make this sort of code work. A workaround exists in today's
singletons-thwhich works well enough:This will make
singletons-thgenerate code that looks like this:{-# LANGUAGE GHC2024 #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeAbstractions #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE UndecidableInstances #-} module Foo where import Data.Functor.Compose import Data.Kind type TyFun :: Type -> Type -> Type data TyFun a b type (~>) :: Type -> Type -> Type type a ~> b = TyFun a b -> Type infixr 0 ~> type Apply :: (a ~> b) -> a -> b type family Apply f x class PFunctor f where type Fmap (h :: a ~> b) (x :: f a) :: f b type FmapSym0 :: (a ~> b) ~> f a ~> f b data FmapSym0 z type instance Apply FmapSym0 h = FmapSym1 h type FmapSym1 :: (a ~> b) -> f a ~> f b data FmapSym1 g z type instance Apply (FmapSym1 h) x = Fmap h x instance PFunctor (Compose (f :: Type -> Type) g) where type Fmap h x = FmapCompose h x type FmapCompose :: forall (f :: Type -> Type) g a b. (a ~> b) -> Compose f g a -> Compose f g b type family FmapCompose g c where FmapCompose h ('Compose x) = 'Compose (Fmap (FmapSym1 h) x)Defunctionalizing declarations using visible dependent quantification
EDIT: This was fixed by #603.
When you promote this type-level declaration:
singletons-thwill generate this code:{-# LANGUAGE GHC2024 #-} {-# LANGUAGE NoStarIsType #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeAbstractions #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE UndecidableInstances #-} module Foo where import Data.Kind import Data.Proxy type TyFun :: Type -> Type -> Type data TyFun a b type (~>) :: Type -> Type -> Type type a ~> b = TyFun a b -> Type infixr 0 ~> type Apply :: (a ~> b) -> a -> b type family Apply f x type SameKind :: k -> k -> Constraint type SameKind a b = (() :: Constraint) type P :: forall k -> k -> Type type P k (a :: k) = Proxy a data PSym0 z where PSym0KindInference :: SameKind (Apply PSym0 arg) (PSym1 arg) => PSym0 k type instance Apply PSym0 k = PSym1 k data PSym1 k :: k ~> Type where PSym1KindInference :: SameKind (Apply (PSym1 k) arg) (PSym2 k arg) => PSym1 k a type instance Apply (PSym1 k) a = PSym2 k a type family PSym2 k (a :: k) :: Type where PSym2 k a = P k a(#overly-polymorphic-lambda-lifting-part-1)GHC will now reject this with:
If this feels familiar, it's because we've already seen this exact same scenario play out before in the Overly polymorphic lambda-lifting, part 1 section. In particular, we have defunctionalization symbols with the following kinds:
Note that the kind of
PSym1uses a visibleforall, whereas the kind ofPSym0does not. Therefore, when GHC kind-checks the left-hand side of this type family instance:GHC concludes that:
Where
k1is distinct fromk. Therefore, when we kind-check the right-hand side:We have a kind error, as
PSym1 khas kindk ~> Type, notk1 ~> Type. (Note thatk1happens to be called "arg" in the error message above.)This time, the trick from #592 (i.e., option (1) in the Overly polymorphic lambda-lifting, part 1 section) won't work, as there is no lambda-lifting happening here. The most viable solution is to implement option (2) in the Overly polymorphic lambda-lifting, part 1 section by generating this code:
Overly polymorphic lambda-lifting, part 2
EDIT: This was fixed by #610.
Neither option (1) nor option (2) from the Overly polymorphic lambda-lifting, part 1 section above are enough to make this example work:
singletons-thwill generate (roughly) the following code when promotingnub:{-# LANGUAGE GHC2024 #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE UndecidableInstances #-} module Foo where import Data.Type.Bool class PEq a where type (x :: a) == (y :: a) :: Bool type Elem :: a -> [a] -> Bool type family Elem x ls where Elem _ '[] = False Elem x (y:ys) = x == y || Elem x ys type family LetScrutinee a x xs ls l where LetScrutinee a x xs ls l = Elem x ls type family Case a x xs ls l t where Case a x xs ls l True = Nub' a l xs ls Case a x xs ls l False = x : Nub' a l xs (x:ls) type family Nub' a l (x :: [a]) (ls :: [a]) :: [a] where Nub' a l '[] _ = '[] Nub' a l (x:xs) ls = Case a x xs ls l (LetScrutinee a x xs ls l) type Nub :: forall a. [a] -> [a] type family Nub l where Nub @a l = Nub' a l l '[]GHC will now reject this:
The problem is that
Case's kind is too polymorphic:Rather than something like:
However, we can do better here. Note that none of the arguments to
Casehave any kind signatures whatsoever:This is silly, because we can determine the kinds of several of these arguments (e.g.,
lsandl) by looking at the syntax ofnub. And indeed, if we sprinkle even a couple of these kind signatures into the definition ofCase:Then GHC accepts the program once more. It should be possible to generate these kind signatures during lambda lifting.
Parts of
singletons-basewhich are affected by this issue are:nub:singletons/singletons-base/src/Data/List/Singletons/Internal.hs
Lines 564 to 569 in 037fc0e
foldMapDefault:singletons/singletons-base/src/Data/Traversable/Singletons.hs
Lines 298 to 303 in 037fc0e
replicateM:singletons/singletons-base/src/Control/Monad/Singletons.hs
Lines 206 to 213 in 037fc0e
replicateM_:singletons/singletons-base/src/Control/Monad/Singletons.hs
Lines 216 to 223 in 037fc0e
Overly polymorphic local bindings
Here is a problematic example, boiled down from the
Singletons/T296.hstest case:This translates to the following code:
{-# LANGUAGE GHC2024 #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeAbstractions #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE UndecidableInstances #-} module Bug where import Data.Kind type family LetX a :: Maybe a where LetX a = 'Nothing type family LetZ a where LetZ a = LetX a type N :: forall a. Maybe a type family N @a :: Maybe a where N @a = LetZ aLetZis problematic. GHC 9.10.1 accepts it, but gives it a counterintuitive kind:Rather than giving it the kind
forall a -> Maybe a, it's given the kindType -> Maybe a, and the type family equation forLetZmatches on the invisible kind argument (LetZ @{a} a = ...). A more recent GHC will reject this with:Some
singletons-basetest suite failures that have similar root causes:Singletons/CaseExpressions.hs:singletons/singletons-base/tests/compile-and-dump/Singletons/CaseExpressions.hs
Lines 25 to 29 in 037fc0e
Singletons/T183.hs:singletons/singletons-base/tests/compile-and-dump/Singletons/T183.hs
Line 53 in 037fc0e
Singletons/T296.hs:singletons/singletons-base/tests/compile-and-dump/Singletons/T296.hs
Lines 9 to 13 in 037fc0e
Singletons/T433.hs:singletons/singletons-base/tests/compile-and-dump/Singletons/T433.hs
Lines 41 to 43 in 037fc0e
Singletons/T581.hs:singletons/singletons-base/tests/compile-and-dump/Singletons/T581.hs
Lines 30 to 32 in 5f0d420
Truth be told, I'm not quite sure what to do about these sorts of examples. The only way I can imagine fixing this examples is by either rewriting the code or by sprinkling type annotations in random parts of the code. You might think that the takeaway here is "local bindings without top-level type signatures are bad", but that's not quite true, since GHC will continue to accept examples like this one:
Sadly, we may just have to accept that GHC's kind inference just isn't up to the task in all cases and advise users to omit type signatures at their peril.