Closed RyanGlScott closed 5 years ago
Without looking deeply into this, my guess is that, generally, we don't want the code to be generalized. Happily, there's an easy way to prevent generalization: use a partial type signature. This will emit warnings, etc., but it should work. In truth, a partial type signature is really what we want -- we know some details about the type (the number of arguments and the fact that all arguments are Sing
s, say) but not others. So, this isn't just a workaround, but semantically justified. The user-visible warnings, etc., are regrettable, but can easily be suppressed with -XPartialTypeSignatures -Wno-partial-type-signatures
.
I conjecture that once we're building with type-variables-can-stand-for-types, we can remove these signatures entirely and use pattern signatures instead.
I conjecture that once we're building with type-variables-can-stand-for-types, we can remove these signatures entirely and use pattern signatures instead.
GHC 8.7 supports type-variables-for-types, so in theory this is possible to test now. That being said, I don't quite understand how pattern signatures would fix this issue.
Using pattern signatures to bind type variables may mean that we do not need to write type signatures on local definitions where the user didn't write one. (My guess is that's the problem. If the user wrote a type signature, I don't think we'll have this issue.)
Apologies for being slow on the uptake here, but I'm having a hard time seeing how this would actually manifest in practice. Let's suppose our goal here is to avoid giving explicit type signatures when singling things that lack them. Consider the following example:
{-# LANGUAGE GADTs #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeInType #-}
{-# LANGUAGE TypeOperators #-}
module Foo where
import Data.Kind
data family Sing :: forall k. k -> Type
data instance Sing :: forall a. [a] -> Type where
SNil :: Sing '[]
SCons :: Sing x -> Sing xs -> Sing (x:xs)
walk [] = []
walk (x:xs) = x:walk xs
type family Walk xs where
Walk '[] = '[]
Walk (x:xs) = x:Walk xs
sWalk :: forall arg. Sing arg -> Sing (Walk arg)
sWalk SNil = SNil
sWalk (sX `SCons` sXs) = sX `SCons` sWalk sXs
How can we remove the explicit type signature for sWalk
? If we try to just remove it as-is, then we'll get lots of untouchable type variable errors from GHC. If I'm reading https://github.com/goldfirere/singletons/issues/357#issuecomment-411614828 correctly, there should be a way to give the patterns in sWalk
signatures that obviate the need for a top-level type signature. That being said, I can't figure out what these pattern signatures should be. This, for instance, doesn't work (and gives mostly the same untouchable type error messages):
-- sWalk :: forall arg. Sing arg -> Sing (Walk arg)
sWalk (SNil :: Sing arg) = SNil
sWalk (sX `SCons` sXs :: Sing arg) = sX `SCons` sWalk sXs
What should I actually be doing?
I was thinking of something like this:
sWalk (sArg :: Sing arg) = (case sArg of
SNil -> SNil
sX `SCons` sXs -> sX `SCons` sWalk sXs) :: Sing (Walk arg)
But that doesn't work! The problem is that sWalk
is polymorphic recursive. And we need a type signature to unlock polymorphic recursion. Even a partial type signature won't do: polymorphic recursion requires a complete type signature -- precisely the bit of syntax that leads to undesirable kind-generalization.
Hrm. We need syntax that allow polymorphic recursion but does not do kind-generalization. Short of disabling -XPolyKinds
(which is clearly disastrous), GHC doesn't have such a syntax.
I'm stuck now. Maybe GHC relaxes the "partial type signatures disallow polymorphic recursion" rule? But I haven't a clue how to do that. Or we introduce syntax that specifically disrequests kind generalization.
This is good fodder for future discussion.
An update on this business: it turns out that after some deliberation, Simon PJ and @goldfirere have come to the conclusion that we should bring back decideKindGeneralisationPlan
to GHC... at least partly. Encouragingly, @goldfirere believes that if this were implemented, a good number of these functions (possibly all of them?) that currently don't compile on GHC HEAD would compile if decideKindGeneralisationPlan
were revived. It might be best to park this issue until that becomes a reality.
Here is a version of the program in https://github.com/goldfirere/singletons/issues/357#issue-348743462 without any dependency on singletons
itself (useful for GHC HEAD testing):
{-# LANGUAGE GADTs #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeInType #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
module Bug where
import Data.Kind
data TyFun :: Type -> Type -> Type
type a ~> b = TyFun a b -> Type
infixr 0 ~>
type family Apply (f :: a ~> b) (x :: a) :: b
data family Sing :: k -> Type
newtype instance Sing (f :: k1 ~> k2) =
SLambda { applySing :: forall t. Sing t -> Sing (Apply f t) }
type SingFunction1 f = forall t. Sing t -> Sing (Apply f t)
singFun1 :: forall f. SingFunction1 f -> Sing f
singFun1 f = SLambda f
class PFoo1 a where
type family Bar1 (x :: a) (y :: a ~> b) :: b
type family Baz1 :: a
class SFoo1 a where
sBar1 :: forall (x :: a) (y :: a ~> b).
Sing x -> Sing y -> Sing (Bar1 x y :: b)
sBaz1 :: Sing (Baz1 :: a)
type family Quux1 (x :: a) :: a where
Quux1 x = Bar1 x (LambdaSym1 x)
sQuux1 :: forall a (x :: a). SFoo1 a => Sing x -> Sing (Quux1 x :: a)
sQuux1 (sX :: Sing x)
= sBar1 sX
((singFun1 @(LambdaSym1 x))
(\ sArg
-> case sArg of {
(_ :: Sing arg)
-> (case sArg of { _ -> sBaz1 }) ::
Sing (Case x arg arg) }))
type family Case x arg t where
Case x arg _ = Baz1
type family Lambda x t where
Lambda x arg = Case x arg arg
data LambdaSym1 x t
type instance Apply (LambdaSym1 x) t = Lambda x t
An update on this whole kerfuffle. Since the root of this issue appears to be an interaction between singletons
and local kind generalization, @goldfirere had a bold idea to fix it: just disable local kind generalization! At least, that's the dream. To realize this dream, @goldfirere suggested using this trick:
f :: Sing @_ Blah
f = ...
In more detail, GHC treats this wildcard type as though one is using a partial type signature (even though one doesn't need to use the PartialTypeSignatures
extension to actually do so). Moreover, whenever GHC detects that a function has a partial type signature, it turns off kind generalization for that function! This is perhaps, ahem, a little dubious, but it would work well for singletons
' purposes, since it would only require that users enable TypeApplications
, which is a much less dubious solution than forcing users to enable PartialTypeSignatures
.
I've been experimenting with trying to port singletons
over to use this trick, but unfortunately, I haven't been able to fix everything, no matter which combination of wildcards I try. When I say "combination", I mean that there are various places in the codebase where one can choose to generate code mentioning @_
, including:
Directly in singType
. This is a non-starter because GHC rejects something like
data instance Sing :: Identity a -> Type where
SIdentity :: Sing @_ x -> Sing ('Identity x)
With the following error:
Wildcard ‘_’ not allowed
in the definition of data constructor ‘SIdentity’
Whether this behavior in GHC is desirable or not is a different matter. But in any case, it means that (at least for the time being) we can't just place @_
willy-nilly—we have to be judicious with our use of wildcard types.
singTySig
, which is responsible for singling the types in a DLetDec
. (There are actually two places in singTySig
that can be changed: the case where the type signature is known, and the case where it is not known.)ADCaseE
case of singExp
. (Currently, this adds an explicit kind signature when provided Just
a kind as an argument. If provided a Nothing
as an argument, we can add a @_
.)No matter what combination of 2 and 3 I try, however, there are still functions that just won't compile. One example that has me absolutely stumped is mfilter
. I've produced a minimal code snippet that reproduces the issue (with no dependencies besides base
) that compiles with GHC 8.6.3 but not with HEAD+VKA:
No matter what combination of @_
s that I sprinkle into mfilter
, I can't seem to make it compile. Can you?
I have done some investigation.
Posting the standalone repro is incredibly helpful. Thank you.
Putting a @_
in the return type does stop kind-generalization here. Sadly, it doesn't solve the problem: the metavariable produced to fill in the _
can't be correctly solved, as the info necessary is inside the case branches, where the _
is untouchable.
We could put type annotations inside the case branches. This would not be hard, I think. But it actually doesn't help us here.
The only solution I found to this problem was dropping the type annotation on case
. But I worry that this will break other things. I tried to test, but I couldn't get singletons/th-desugar/GHC to all play nicely together.
And now, something unexpected: If I put a :: _
on the case
, a working program changes to not work. But that really shouldn't happen -- adding :: _
should be a no-op. I'll investigate this aspect further. Perhaps we have a GHC bug.
Posted GHC#16146. Note that this isn't necessarily a bug in GHC -- it's more a question about design principles around partial type signatures and expression signatures. See the GHC ticket.
Next step (@RyanGlScott ?): try just dropping signatures on case
expressions to see what happens. That will fix this. But is the fix worse than the disease?
Alas, dropping signatures on case
expressions opens up a different can of worms. The proximal cause was showParen
failing to compile, which I reduced down to this example:
$ ~/Software/ghc4/inplace/bin/ghc-stage2 Bug.hs
[1 of 1] Compiling Bug ( Bug.hs, Bug.o )
Bug.hs:75:5: error:
• Couldn't match type ‘Apply f0 t_a3316’
with ‘Apply
(Case_6989586621679736371_a3312 t_a3314 t_a3315 t_a3316 t_a3314)
t_a3316’
Expected type: Sing
(Apply (Apply (Apply HmSym0 t_a3314) t_a3315) t_a3316)
Actual type: Sing (Apply f0 t_a3316)
NB: ‘Apply’ is a non-injective type family
The type variable ‘f0’ is ambiguous
• In the expression:
(applySing
((case sB of
STrue -> sP
SFalse -> sP)))
sA_6989586621679736366
In an equation for ‘sHm’:
sHm
(sB :: Sing b_a330Y)
(sP :: Sing p_a330Z)
(sA_6989586621679736366 :: Sing a_6989586621679736366_a3310)
= (applySing
((case sB of
STrue -> sP
SFalse -> sP)))
sA_6989586621679736366
• Relevant bindings include
sA_6989586621679736366 :: Sing t_a3316 (bound at Bug.hs:74:4)
sP :: Sing t_a3315 (bound at Bug.hs:73:4)
sB :: Sing t_a3314 (bound at Bug.hs:72:4)
sHm :: Sing t_a3314
-> Sing t_a3315
-> Sing t_a3316
-> Sing (Apply (Apply (Apply HmSym0 t_a3314) t_a3315) t_a3316)
(bound at Bug.hs:71:1)
|
75 | = (applySing
| ^^^^^^^^^^...
Bug.hs:77:20: error:
• Couldn't match type ‘f0’ with ‘t_a3315’
‘f0’ is untouchable
inside the constraints: t_a3314 ~ 'True
bound by a pattern with constructor: STrue :: Sing 'True,
in a case alternative
at Bug.hs:77:11-15
‘t_a3315’ is a rigid type variable bound by
the type signature for:
sHm :: forall (t_a3314 :: Bool) (t_a3315 :: () ~> ())
(t_a3316 :: ()).
Sing t_a3314
-> Sing t_a3315
-> Sing t_a3316
-> Sing (Apply (Apply (Apply HmSym0 t_a3314) t_a3315) t_a3316)
at Bug.hs:(65,1)-(70,84)
Expected type: Sing f0
Actual type: Sing t_a3315
• In the expression: sP
In a case alternative: STrue -> sP
In the first argument of ‘applySing’, namely
‘((case sB of
STrue -> sP
SFalse -> sP))’
• Relevant bindings include
sP :: Sing t_a3315 (bound at Bug.hs:73:4)
sHm :: Sing t_a3314
-> Sing t_a3315
-> Sing t_a3316
-> Sing (Apply (Apply (Apply HmSym0 t_a3314) t_a3315) t_a3316)
(bound at Bug.hs:71:1)
|
77 | STrue -> sP
| ^^
Bug.hs:78:21: error:
• Couldn't match type ‘f0’ with ‘t_a3315’
‘f0’ is untouchable
inside the constraints: t_a3314 ~ 'False
bound by a pattern with constructor: SFalse :: Sing 'False,
in a case alternative
at Bug.hs:78:11-16
‘t_a3315’ is a rigid type variable bound by
the type signature for:
sHm :: forall (t_a3314 :: Bool) (t_a3315 :: () ~> ())
(t_a3316 :: ()).
Sing t_a3314
-> Sing t_a3315
-> Sing t_a3316
-> Sing (Apply (Apply (Apply HmSym0 t_a3314) t_a3315) t_a3316)
at Bug.hs:(65,1)-(70,84)
Expected type: Sing f0
Actual type: Sing t_a3315
• In the expression: sP
In a case alternative: SFalse -> sP
In the first argument of ‘applySing’, namely
‘((case sB of
STrue -> sP
SFalse -> sP))’
• Relevant bindings include
sP :: Sing t_a3315 (bound at Bug.hs:73:4)
sHm :: Sing t_a3314
-> Sing t_a3315
-> Sing t_a3316
-> Sing (Apply (Apply (Apply HmSym0 t_a3314) t_a3315) t_a3316)
(bound at Bug.hs:71:1)
|
78 | SFalse -> sP)
| ^^
Blargh. I thought that might happen.
Goal: Stop GHC from kind-generalizing type signatures.
Approach proposed: By insertion of a wildcard, GHC will avoid kind-generalizing a type signature. But then GHC will type-generalize the wildcard. We thus have
Secondary goal: Stop GHC from type-generalizing partial type signatures.
Approach proposed: ???
There doesn't seem to be a way to stop this. I thought that using let-should-not-be-generalized would work, but as the last example here shows, let-should-not-be-generalized doesn't apply when you use a partial type signature. Yet we still need the partial type signature to stop kind-generalization.
I'm stuck. :(
As one last crazy idea, we could add some variant of singletons
that accepts an Options
argument that allows finer tuning of whether signatures/wildcards are generated or not, and then pick the combination that works the best for each problematic definition. This is a pretty awful solution, but I literally can't think of any other way to approach this (short of changing GHC's implementation of kind inference).
What about this: don't generate the type signatures on case
, requiring users to write their own in the few places where it's necessary? That's similar to your idea, but much easier to explain and implement.
I'm not sure what you mean here. How would a user be able to manually insert a type signature into generated code?
By putting a signature in the original code. This signature would be clearly redundant in the original, but it's necessary in our output. Maybe this doesn't work at all, but it's worth a try, I think.
Actually, that just might work! As you predicted, I had to add some signatures manually in various places, but once I did that, then singletons
compiles and passes its tests! (Modulo style changes in -ddump-splices
output introduced in GHC 8.8, of course.) For reference, here are all of the changes I had to make in order for the generated code to typecheck:
The reason I eta-expanded showParen
is due to #376, which I think actually singletons
' fault, not GHC's.
One rather unfortunate aspect of all of this (aside from, y'know, the breakage) is that it's rather difficult to predict what code will need extra signatures in order to compile. For instance, if you look at the top of the diff above, you'll notice that I had to change some generated code for derived Show
and Traversable
instances that uses EmptyCase
. Curiously, I did not have to change the generated code for derived Functor
instances, even though it also uses EmptyCase
. It's unclear to me if this is due to some fundamental difference between these classes, or if I just got lucky and only tested a data type that happened not to need an explicit signature in its generated Functor
instance. (If it's the latter, then perhaps we ought to perform an audit of all uses of DCaseE
in singletons
and make a judgment on whether each one deserves a signature.)
I don't have a good intuition for this either. We could just wait for bug reports to come in... Of course, adding a redundant signature doesn't really hurt anyone, so if you know how to do it (and it doesn't take much time), then go for it.
Glad to know we have a way forward. And it wasn't that much breakage. We'll have to have a loud comment in the README warning users.
Hold the presses! I've just had an idea!
The original problem is kind-generalization. So we came up with @_
as a way to stop that. And chaos ensued.
Here's a different way: don't use a type signature. Instead, use id
. That is, instead of producing expr :: ty
to write a type signature in code, use id @ty expr
. Visible type application arguments do not get kind-generalized. No partial type signatures anywhere.
Have cake, eat too.
I'm continually impressed by your ability to snatch victory from the jaws of defeat! Yes, this works out to be much nicer in practice—now I don't have to touch the code in D.S.Deriving.*
at all. I do still have to make the following changes:
diff --git a/src/Data/Singletons/Prelude/List/Internal.hs b/src/Data/Singletons/Prelude/List/Internal.hs
index f6e7a4a..84cb4bf 100644
--- a/src/Data/Singletons/Prelude/List/Internal.hs
+++ b/src/Data/Singletons/Prelude/List/Internal.hs
@@ -96,11 +96,6 @@ $(singletonsOnly [d|
perms (t:ts) is = foldr interleave (perms ts (t:is)) (permutations is)
where interleave xs r = let (_,zs) = interleave' id xs r in zs
- -- This type signature isn't present in the reference
- -- implementation of permutations in base. However, it is needed
- -- here, since (at least in GHC 8.2.1) the singletonized version
- -- will fail to typecheck without it. See #13549 for the full story.
- interleave' :: ([a] -> b) -> [a] -> [b] -> ([a], [b])
interleave' _ [] r = (ts, r)
interleave' f (y:ys) r = let (us,zs) = interleave' (f . (y:)) ys r
in (y:us, f (t:y:us) : zs)
diff --git a/src/Data/Singletons/Prelude/Monad.hs b/src/Data/Singletons/Prelude/Monad.hs
index f3a490b..0d65769 100644
--- a/src/Data/Singletons/Prelude/Monad.hs
+++ b/src/Data/Singletons/Prelude/Monad.hs
@@ -208,19 +208,21 @@ $(singletonsOnly [d|
-- -| @'replicateM' n act@ performs the action @n@ times,
-- gathering the results.
- replicateM :: (Applicative m) => Nat -> m a -> m [a]
+ replicateM :: forall m a. (Applicative m) => Nat -> m a -> m [a]
replicateM cnt0 f =
loop cnt0
where
+ loop :: Nat -> m [a]
loop cnt
| cnt <= 0 = pure []
| otherwise = liftA2 (:) f (loop (cnt - 1))
-- -| Like 'replicateM', but discards the result.
- replicateM_ :: (Applicative m) => Nat -> m a -> m ()
+ replicateM_ :: forall m a. (Applicative m) => Nat -> m a -> m ()
replicateM_ cnt0 f =
loop cnt0
where
+ loop :: Nat -> m ()
loop cnt
| cnt <= 0 = pure ()
| otherwise = f *> loop (cnt - 1)
diff --git a/src/Data/Singletons/Prelude/Semigroup/Internal.hs b/src/Data/Singletons/Prelude/Semigroup/Internal.hs
index 3eadc90..527c639 100644
--- a/src/Data/Singletons/Prelude/Semigroup/Internal.hs
+++ b/src/Data/Singletons/Prelude/Semigroup/Internal.hs
@@ -75,6 +75,7 @@ $(singletonsOnly [d|
--
sconcat :: NonEmpty a -> a
sconcat (a :| as) = go a as where
+ go :: a -> [a] -> a
go b (c:cs) = b <> go c cs
go b [] = b
But this is a far cry from the signature acrobatics that we had to employ on our previous attempts, so I think this is relatively tolerable. We'll still need to put a disclaimer of some sort in the README
, but most folks shouldn't be too frightened by the prospect of giving local definitions explicit type signatures.
Hilariously enough, I was able to remove the type signature for interleave'
that I had to add to work around a previous change in GHC's kind generalization behavior (Trac #13555). This goes to show that every dark cloud has a silver lining after all :)
I'm surprised any signatures have to be added, at all. We can re-add the signature (er, call to id
) on all case
statements now, which should mean that we don't need those signatures. Or am I missing something?
I'm not sure myself why those definitions require signatures. If it's any help, here is the sconcat
example boiled down to a single file:
Oh wait, I know exactly why those require signatures. (In fact, I chose a modified version of this example to put in the GHC 8.8 Migration Guide!)
Here is what is going on. In the return type of sGo
(Sing (Apply (Apply (Let6989586621679186784GoSym2 a_aK2G as_aK2H) arg_aK2V) arg_aK2W)
), it must be the case that Let6989586621679186784GoSym2 a_aK2G as_aK2 :: a_aK2q ~> [a_aK2q] ~> a_aK2q
. But due to local kind generalization, we instead get Let6989586621679186784GoSym2 a_aK2G as_aK2 :: k1 ~> [k1] ~> k1
(for some fresh kind variable k1
). This causes the program to no longer compile, since GHC can't figure out that k1
should be the same as a_aK2q
.
Notice that unlike previous examples, this didn't arise from a case
expression—this is simply a local definition behaving badly. As such, I don't see a viable workaround besides giving it a signature.
After GHC commit c955a514f033a12f6d0ab0fbacec3e18a5757ab5 (
Remove decideKindGeneralisationPlan
),singletons
no longer builds on GHC HEAD. I originally thought this was a bug and opened Trac #15472 for this, but it was determined that this was expected behavior. Thus, I'm opening this ticket to determine how to changesingletons
to accommodate the lack ofdecideKindGeneralisationPlan
.I originally thought that patching
singletons
would be simple, as shown in https://ghc.haskell.org/trac/ghc/ticket/15472#comment:3. However, it seems that I was mistaken—I vastly underestimated the amount of subtle interactions thatsingletons
has with kind generalization. I've encountered one major issue that I have no idea how to fix, and one minor issue that has a relatively straightforward fix.Major issue
The following parts of
singletons
all fail due an interaction between kind generalization, lambdas, and case expressions:Monoid (a -> b)
instancemfilter
T136
test case (specifically, theotherwise
case of thetoEnum
function)T176
test case (specifically, thequux1
function)T184
test case (specifically, thezip'
function)Of these,
T176
is probably the most easily digestible. Here's essentially what is going on in that program:This is failing because the return kind of
Case x arg arg
(in:: Sing (Case x arg arg)
) is being generalized tok
, but it should bea
.I'm really not sure what to do here. We certainly need that explicit
:: Sing (...)
type signature, or else other parts ofsingletons
won't compile. The only workaround that I've discovered is to use an explicit kind signature ofCase x arg arg :: a
, but I have no idea how to communicate this typing information to that part of the code.Minor issue
Some other changes in kind generalization behavior can be worked around by simply giving explicit type signatures to local definitions. These include:
In
sconcat
, give this type signature togo
:In
replicateM
, give this type signature toloop
:(Also add
forall m a.
to the front of the type signature forreplicateM
.)In
replicateM_
, give this type signature toloop
:(Also add
forall m a.
to the front of the type signature forreplicateM_
.)In
permutations
, give this type signature toperms
:And this type signature to
interleave
:(Curiously,
interleave
does not need an explicit type signature insingletons-2.4.1
, only insingletons
HEAD. It's possible that the fact thatsingletons
HEAD explicitly quantifies all kind variables makes a difference.)