-
Notifications
You must be signed in to change notification settings - Fork 36
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
Promoting expression signatures can fail on GHC 8.10+ #433
Comments
This bug is another obstacle in the way of fixing chunk (3) of #378 (comment). If this doesn't promote: $(singletons [d|
g :: forall a. a -> a
g (x :: b) = id (x :: b)
|]) Then this will also be difficult to promote: $(singletons [d|
g :: forall a. a -> a
g (x :: b) = id @b x
|]) |
Could you go through the type and manually add kind signatures to all arguments? Or we could use syntax like
Note that I didn't even need to put kind signatures on the Separately, I think it's a bug that kind signatures in type family equation left-hand sides are not pattern-like. Perhaps fixing that in GHC is another way forward. |
There have been three potential remedies proposed so far:
I would characterize all of these solutions as roughly equivalent. They all fix
Unfortunately, this means that none of the remedies quite get us back to the previous behavior.
I agree. In fact, I proposed this at one point, but it was deemed a "red herring" at the time. I think the herring has finally changed color! |
Sigh. singletons/src/Data/Singletons/Prelude/List/Internal.hs Lines 648 to 649 in 94a1054
When promoted, this turns into: type family GenericTake (x :: i) (y :: [a]) :: [a] where
GenericTake x y = TakeSym0 @@ x @@ y Seems straightforward enough. What happens if we try binding the kind variables as well? type family GenericTake (x :: i) (y :: [a]) :: [a] where
GenericTake @i @a x y = TakeSym0 @@ x @@ y After doing this,
As it turns out, |
But that |
Note that singletons/src/Data/Singletons/Prelude/List/Internal.hs Lines 641 to 648 in 94a1054
It would indeed fail to compiled in singled form. |
It also is not accepted in unsingled form -- in plain Haskell, it's meaningless. We are not in the business of taking ill-typed code and magically making it work. Either we strip these functions from our library or update their type signatures to be correct (and not polymorphic). I vote the former. |
I'd be fine with removing these definitions as well. They've given me headaches in the past, but I never had a solid excuse to remove them until now. (My apologies to Jan, who originally added them in b0542d6.) Still, there may be a future where promoted functions could conceivably match on invisible arguments. After all, there has been talk of supporting GADTs in |
Per the discussion in #433 (comment), these definitions are subtly bogus. Let's just remove them.
How so? |
What happens if you promote this code? sym :: a :~: b -> b :~: a
sym Refl = Refl Using today's promotion algorithm, you would get something like this: type family Sym (x :: a :~: b) :: b :~: a where
Sym Refl = Refl This is fine. But if you explicitly bind the kind variables type family Sym (x :: a :~: b) :: b :~: a where
Sym @a @b Refl = Refl Then GHC gripes thusly:
In order to do this correctly, we'd have to infer that |
Per the discussion in #433 (comment), these definitions are subtly bogus. Let's just remove them.
I still like option (3) of #433 (comment). The problem you ran into there is very superficial: either we teach GHC to warn (not error) on unused bound variables in that position, or we teach |
Option (3) might work if we scrape away any bound variables that aren't mentioned in a type family equation, but it will be tricky to get right. Here is a corner case: {-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
module Bug where
import Data.Singletons.Prelude
import Data.Singletons.TH
$(singletons [d|
f :: forall p. p -> p
f = g
where
g = id :: p -> p
|]) Currently, this promotes to approximately the following code (ignore the type family F (a :: p) :: p where
forall a. F a = Apply (Let123Sym1 a) a
...
type family Let123 a where
forall a. Let123 a = IdSym0 Suppose that we wanted to preserve the type family F (a :: p) :: p where
forall p a. F a = Apply (Let123Sym2 p a) a
...
type family Let123 p a where
forall p a. Let123 p a = IdSym0 :: p ~> p |
Can you double-check that? What is the variable But it would stand to reason that, if we have to do special munging with term-level variables in lambda-lifting, we would have to do the same with type-level variables. |
Yes, this is taken directly from the output of
When singling a function, f :: forall p. p -> p
f a = g a
where
g = id :: p -> p You're correct in observing that
I'm not sure what you mean by "special munging", but it is certainly true that we lambda-lift term variables. I'm simply proposing that we lambda-lift type variables as well. |
OK. Thanks for the explanation. Yes, we will have to close over scoped type variables when we lambda-lift, as this example demonstrates. But any treatment of expression type signatures will require careful work around scoped type variables... |
I've encountered a tricky example that I'm not sure how to deal with: $(promote [d|
foo :: forall a. a -> ()
foo x = const () (Nothing :: Maybe a)
|]) This is a strange-looking function, but type family Foo (x :: a) :: () where
forall a x. Foo x = Const '() (Nothing :: Maybe a) This, unfortunately, does not typecheck:
That error message is strange (it refers to A tempting solution is to annotate $(promote [d|
foo :: forall a. a -> ()
foo (x :: b) = const () (Nothing :: Maybe a)
|]) Then you'd generate this: type family Foo (x :: a) :: () where
forall a b x. Foo ((x :: b) :: a) = Const '() (Nothing :: Maybe a) Which also doesn't typecheck:
In effect, this would prevent you from promoting any pattern-like type variables (unless they happened to be exactly the same as the argument type, e.g. |
In fact, the annotate-arguments-with-their-kinds trick in #433 (comment) won't always work. Here is a counterexample: $(singletons [d|
bar :: forall a. ()
bar = const () (Nothing :: Maybe a)
|]) If we tried promoting type Bar :: forall a. ()
type family Bar where
forall a. Bar = Const '() (Nothing :: Maybe a) Then just like
Moreover, type Bar :: forall a. ()
type family Bar where
Bar @a = Const '() (Nothing :: Maybe a) |
All of this makes me wonder: is supporting pattern-like variables in $(singletons [d|
baz :: a -> a
baz (x :: b) = x
|]) Are doomed to fail (unless something changes in the way GHC typechecks type family equations with pattern-like variables). This would be a breaking change, but then again, |
If the feature is hard to support because GHC is stupid, then I'm OK with dropping the feature. The "stupid" bit is the fact that we have pattern-like variables in terms but not in types. |
Alright. To summarize the events so far:
Unfortunately, I just discovered another roadblock. Here is an extra spicy example: $(promote [d|
f local = g
where
g :: forall a. a -> a
g x = const (x :: a) local
|]) If we use visible kind applications, then type family Let123 local (x :: a) :: a where
Let123 @a local x = Const (x :: a) local Notice that
I'm honestly somewhat shocked that this doesn't work, since the kind of |
Visible kind application in an equation is essentially a form of polymorphic recursion. And that's not allowed unless you have a CUSK or SAK. |
Uh oh. That might be the final nail in the coffin for making this work in the GHC of today (i.e., 8.10), since |
This patch fleshes out some more details about what `singletons` can and can't do in its `README`. The key changes are: 1. There is a new "Promotion and partial application" section that explains what defunctionalization is in some amount of detail. There is also a new subsection that explains the limitations of the `genDefunSymbols` function that were observed in #429. 2. The "Supported Haskell constructs" section has received some more love. Some Haskell features were inaccurately characterized (e.g., pattern signatures are really only partially supported), so I also reorganized some of the bullet points. I have also added a new bullet point for `ScopedTypeVariables` under the "Little to no support" section, as #433 reveals that promoting functions that rely on the behavior of `ScopedTypeVariables` is terribly fragile (and not easy to fix). 3. Lots of little formatting and grammar fixes to make the prose in the `README` flow better. Note that this patch does _not_ fix either of #429 or #433—it just documents the rather unsatisfying current state of affairs.
For now, I have decided to just add some documentation to the |
This patch fleshes out some more details about what `singletons` can and can't do in its `README`. The key changes are: 1. There is a new "Promotion and partial application" section that explains what defunctionalization is in some amount of detail. There is also a new subsection that explains the limitations of the `genDefunSymbols` function that were observed in #429. 2. The "Supported Haskell constructs" section has received some more love. Some Haskell features were inaccurately characterized (e.g., pattern signatures are really only partially supported), so I also reorganized some of the bullet points. I have also added a new bullet point for `ScopedTypeVariables` under the "Little to no support" section, as #433 reveals that promoting functions that rely on the behavior of `ScopedTypeVariables` is terribly fragile (and not easy to fix). 3. Lots of little formatting and grammar fixes to make the prose in the `README` flow better. Note that this patch does _not_ fix either of #429 or #433—it just documents the rather unsatisfying current state of affairs.
) This patch fleshes out some more details about what `singletons` can and can't do in its `README`. The key changes are: 1. There is a new "Promotion and partial application" section that explains what defunctionalization is in some amount of detail. There is also a new subsection that explains the limitations of the `genDefunSymbols` function that were observed in #429. 2. The "Supported Haskell constructs" section has received some more love. Some Haskell features were inaccurately characterized (e.g., pattern signatures are really only partially supported), so I also reorganized some of the bullet points. I have also added a new bullet point for `ScopedTypeVariables` under the "Little to no support" section, as #433 reveals that promoting functions that rely on the behavior of `ScopedTypeVariables` is terribly fragile (and not easy to fix). 3. Lots of little formatting and grammar fixes to make the prose in the `README` flow better. Note that this patch does _not_ fix either of #429 or #433—it just documents the rather unsatisfying current state of affairs.
I originally opened this issue in the wake of GHC proposal #103, which removes all implicit quantification of kind variables on the right-hand sides of type family equations. Well, almost all implicit quantification—there was still an exception for outermost kind variables, such as in the example below: type family F :: Maybe a where
F = Nothing :: Maybe a Well, I'm here to report that that exception has been removed in GHC 9.8, which implements GHC proposal #425. What surprised me is that this regresses singletons/singletons-base/tests/compile-and-dump/Singletons/T183.hs Lines 52 to 54 in b6ca612
Because foo8 :: forall a. Maybe a -> Maybe a
foo8 x@(Just (_ :: a) :: Maybe a) = x
foo8 x@(Nothing :: Maybe a) = x foo8 :: forall a. Maybe a -> Maybe a
foo8 (Just (wild :: a) :: Maybe a) =
let x = Just (wild :: a) :: Maybe a
in x
foo8 (Nothing :: Maybe a) =
let x = Nothing :: Maybe a
in x When promoting the type family LetX :: Maybe a where
LetX = Nothing :: Maybe a This relies on outermost kind variables being implicitly quantified to kind-check, so this no longer works in GHC 9.8! As such, if we want to avoid |
Here is a half-baked (read: not yet implemented) plan:
I think that would cover most of the useful examples in this thread. Am I overlooking anything? |
Ugh, yes, I am overlooking something. Recall the extra-spicy example from #433 (comment): f local = g
where
g :: forall a. a -> a
g x = const (x :: a) local What makes this so spicy is that type G :: forall a. forall local -> a -> a
type family G local x where
G local x = Const (x :: a) local Notice that I am using visible dependent quantification to bind the -- This is essentially just `Const` with a different name
type WithLocals x y = x
type G :: forall a. local -> a -> a `WithLocals` local This trick is fragile, however. Here is a slight variant of this example to increase the spice one level further: konst :: a -> Bool -> a
konst x _ = x
f local = g
where
g :: forall a. a -> a
g x = konst (x :: a) local This time, type Konst :: a -> Bool -> a
type family Konst x y where
Konst x _ = x
type G :: forall a. forall local -> a -> a
type family G local x where
G local x = Konst (x :: a) local Here is where things get dicey. Because type G :: forall {k} a. forall (local :: k) -> a -> a Note that type G :: forall {k} a. forall (local :: k) -> a -> a
type family G local x where
G @Bool (local :: Bool) x = Konst (x :: a) local But note that the left-hand side of the original question lacks the What should we do about all this? I suppose |
The challenge is to promote f local = g
where
g :: forall a. a -> a
g x = konst (x :: a) local The text above suggests that it's the scoped type variable that causes trouble. Why is that? That is, I would imagine any signature on Sadly, this thinking aloud doesn't end in a solution, but maybe it help further the conversation. |
Fair enough, there are actually two distinct (but related) issues here:
Issue (1) is specifically about local functions that bind scoped type variables, but issue (2) arises for any local function that has a type signature. Currently, My half-baked plan in #433 (comment) attempts to address both issues. It attempts to address issue (1) by binding scoped type variables by promoting them to uses of
Yes, that's a key part of issue (1).
Indeed, we could fall back to the hacky solution for issue (2) when a function does not bind any scoped type variables—that would be no worse that the current status quo. (Of course, the extra-spicy given in #433 (comment) does bind a scoped type variable in
Indeed, if we could promote type G a local (x :: a) :: a where
G a local (x :: a) = Konst (x :: a) local Then we wouldn't need to write
One thing that popped into my mind while writing this up: how could we change GHC to make this easier? My first thought was implementing updated partial type signatures, which would let us more directly specify that things like konst :: a -> Bool -> a
konst x _ = x
type KindOf (a :: k) = k
type family Any :: k
g :: forall (local :: KindOf @_ Any) a. a -> a
g x = konst (x :: a) @local Then Sadly, the same trick doesn't work in standalone kind signatures. If you write this: type G :: forall (local :: KindOf @_ Any) a. a -> a Then you run into this GHC issue. So perhaps fixing that issue would provide one relatively quick way to make this work, without the need to implement a large-scale proposal like updated partial type signatures. |
OK. That all makes some sense. The quick way you highlight at the end of your comment won't work. Even if we had wildcards in kind signatures, the presence of any such wildcard would disable polymorphic recursion, and we'd be back to square 1. (Wildcards in kind signatures would be lovely, but they wouldn't solve this problem.) Updated partial type signatures likely would solve this problem, but I agree that's a long way off. ... wandered off in several directions, none of which worked ... Tell me more about hacky approach (2). Going back to our challenge f local = g
where
g :: forall a. a -> a
g x = konst (x :: a) local what if we promote type family LetG local x where
LetG local (x :: a) = Konst (x :: a) local ? The (Digression: though I'm now worried about f :: forall a. a -> a
f x = g x True
where
g :: b -> a -> b
g y _ = y where the This new approach wouldn't work if the only mention of a scoped type variable is in the result, because of upcoming changes to GHC, which require all type variables brought into scope in a type family equation to appear to the left of the type family F x where
F x :: a = x where that What do you think? |
Ah, this is an interesting idea. As you say later in the comment, this wouldn't work in situations where the scoped type variable is only mentioned in the result. And in fact, this is exactly what happens in the I suppose we could combine your approach (attach explicit kind signatures to type family arguments whenever possible) with mine (use type variables as required arguments in lambda-lifting, plus use f local = g
where
g :: forall a. Nothing a
g = konst (Nothing :: a) local But as you say, we could get there if type family left-hand sides supported result signatures. So I think I'm on board. I'll see what happens when I implement this.
A good thing to worry about. I tried splicing such a function into GHC using a TH quote (ensuring that
Those two type family G a1 x (y :: b) (z :: a2) :: b where
G a1 x (y :: b) (z :: a2) = y Which shouldn't pose any issues. |
Good. This does seem like a way forward, imperfect though it is. |
I've made an attempt at implementing this in the
What's more, if you copy the You can hack around this issue by tweaking the code in diff --git a/singletons-base/src/Data/Traversable/Singletons.hs b/singletons-base/src/Data/Traversable/Singletons.hs
index 65e226e..5f91f4c 100644
--- a/singletons-base/src/Data/Traversable/Singletons.hs
+++ b/singletons-base/src/Data/Traversable/Singletons.hs
@@ -297,7 +297,7 @@ $(singletonsOnly [d|
foldMapDefault :: forall t m a . (Traversable t, Monoid m)
=> (a -> m) -> t a -> m
foldMapDefault f x = case traverse (mkConst . f) x of Const y -> y
- where
- mkConst :: m -> Const m ()
- mkConst = Const
+
+ mkConst :: m -> Const m ()
+ mkConst = Const
|]) That's enough to get a complete build of Our usual workaround for GHC#11812 is to sprinkle uses of |
Here is a workaround for the panics seen in #433 (comment): diff --git a/singletons-th/src/Data/Singletons/TH/Promote.hs b/singletons-th/src/Data/Singletons/TH/Promote.hs
index 300347c..7618dee 100644
--- a/singletons-th/src/Data/Singletons/TH/Promote.hs
+++ b/singletons-th/src/Data/Singletons/TH/Promote.hs
@@ -760,7 +760,7 @@ promoteLetDecRHS rhs_sort type_env fix_env mb_let_uniq name let_dec_rhs = do
m_fixity = OMap.lookup name fix_env
mk_tf_head :: [DTyVarBndrUnit] -> DFamilyResultSig -> DTypeFamilyHead
- mk_tf_head tvbs res_sig = DTypeFamilyHead proName tvbs res_sig Nothing
+ mk_tf_head tvbs res_sig = DTypeFamilyHead proName (noExactTyVars tvbs) (noExactTyVars res_sig) Nothing
(lde_kvs_to_bind, m_sak_dec, defun_ki, tf_head) =
-- There are three possible cases:
@@ -1093,7 +1093,7 @@ promoteExp (DCaseE exp matches) = do
tyvarName <- qNewName "t"
let all_args = all_locals ++ [tyvarName]
tvbs = map (`DPlainTV` ()) all_args
- emitDecs [DClosedTypeFamilyD (DTypeFamilyHead caseTFName tvbs DNoSig Nothing) eqns]
+ emitDecs [DClosedTypeFamilyD (DTypeFamilyHead caseTFName (noExactTyVars tvbs) DNoSig Nothing) eqns]
-- See Note [Annotate case return type] in Single
let applied_case = prom_case `DAppT` exp'
return ( applied_case
diff --git a/singletons-th/src/Data/Singletons/TH/Util.hs b/singletons-th/src/Data/Singletons/TH/Util.hs
index c0600fc..a4e0171 100644
--- a/singletons-th/src/Data/Singletons/TH/Util.hs
+++ b/singletons-th/src/Data/Singletons/TH/Util.hs
@@ -390,8 +390,12 @@ noExactTyVars = everywhere go
-- changes a Name not to be a NameU. Workaround for GHC#11812/#17537/#19743
noExactName :: Name -> Name
-noExactName (Name (OccName occ) (NameU unique)) = mkName (occ ++ show unique)
-noExactName n = n
+noExactName (Name (OccName occ) nf)
+ | NameU unique <- nf = mk_name unique
+ | NameL unique <- nf = mk_name unique
+ where
+ mk_name u = mkName (occ ++ show u)
+noExactName n = n
substKind :: Map Name DKind -> DKind -> DKind
substKind = substType The part that I was missing was extending While this is a serviceable workaround, it does degrade the quality of the Haddocks a bit, since now you'd end up with things like |
`singletons-th` can now promote a good number of uses of scoped type variables, save for the two exceptions now listed in the `singletons` `README`. This is accomplished by a combination of: 1. Bringing scoped type variables into scope on the left-hand sides of promoted type family equations through invisible `@` arguments when a function uses an outermost `forall` in its type signature (and does not close over any local variables). 2. Bringing scoped type variables into scope on the left-hand sides of promoted type family equations through explicit kind annotations on each of the type family's arguments when a function has an outermost `forall` in its type signature. 3. Closing over scoped type variables when lambda-lifting to ensure that the type variables are accessible when scoping over local definitions. See the new `Note [Scoped type variables]` in `Data.Singletons.TH.Promote.Monad` for more about how this is implemented. Fixes #433.
`singletons-th` can now promote a good number of uses of scoped type variables, save for the two exceptions now listed in the `singletons` `README`. This is accomplished by a combination of: 1. Bringing scoped type variables into scope on the left-hand sides of promoted type family equations through invisible `@` arguments when a function uses an outermost `forall` in its type signature (and does not close over any local variables). 2. Bringing scoped type variables into scope on the left-hand sides of promoted type family equations through explicit kind annotations on each of the type family's arguments when a function has an outermost `forall` in its type signature. 3. Closing over scoped type variables when lambda-lifting to ensure that the type variables are accessible when scoping over local definitions. See the new `Note [Scoped type variables]` in `Data.Singletons.TH.Promote.Monad` for more about how this is implemented. Fixes #433.
`singletons-th` can now promote a good number of uses of scoped type variables, save for the two exceptions now listed in the `singletons` `README`. This is accomplished by a combination of: 1. Bringing scoped type variables into scope on the left-hand sides of promoted type family equations through invisible `@` arguments when a function uses an outermost `forall` in its type signature (and does not close over any local variables). 2. Bringing scoped type variables into scope on the left-hand sides of promoted type family equations through explicit kind annotations on each of the type family's arguments when a function has an outermost `forall` in its type signature. 3. Closing over scoped type variables when lambda-lifting to ensure that the type variables are accessible when scoping over local definitions. See the new `Note [Scoped type variables]` in `Data.Singletons.TH.Promote.Monad` for more about how this is implemented. Fixes #433.
`singletons-th` can now promote a good number of uses of scoped type variables, save for the two exceptions now listed in the `singletons` `README`. This is accomplished by a combination of: 1. Bringing scoped type variables into scope on the left-hand sides of promoted type family equations through invisible `@` arguments when a function uses an outermost `forall` in its type signature (and does not close over any local variables). 2. Bringing scoped type variables into scope on the left-hand sides of promoted type family equations through explicit kind annotations on each of the type family's arguments when a function has an outermost `forall` in its type signature. 3. Closing over scoped type variables when lambda-lifting to ensure that the type variables are accessible when scoping over local definitions. See the new `Note [Scoped type variables]` in `Data.Singletons.TH.Promote.Monad` for more about how this is implemented. Fixes #433.
singletons
used to have no trouble promoting this code:However, if you try compiling this with GHC 8.10, you'll get the following error:
Big yikes. What changed? Inspecting the
-ddump-simpl
reveals that this is howf
is promoted:Note that on GHC 8.10 or later, the
a_a83H
inApply IdSym0 (x_a83M :: a_a83H)
is out of scope due to GHC Proposal 103 having been implemented. Unfortunately, this presents another incongruity between terms and types thatsingletons
must bridge, since not even givingF
a standalone kind signature would bringa_a83H
into scope.One possibile way forward here is to use visible kind applications to bind kind variables. That is, promote
F
like this instead:This works in that particular example, but there is danger on the horizon. What about this code?
This works today in both GHC 8.8 and 8.10. However, if we adopt our proposed redesign above, we would end up promoting
g
like this:While we can make
b
an alias fora
at the term level, the same trick is not possible at the type level, as this type error proves:I'm honestly not sure how we can restore
singletons
' previous expressivity vis-à-vis expression signatures going forward. Does anyone have any suggestions?The text was updated successfully, but these errors were encountered: