Closed RyanGlScott closed 2 years ago
Upon further thought, solution (2) won't work in the presence of GADTs. Given this data type:
data T a where
MkT :: Int -> T Int
This will typecheck:
f :: T a -> T a -> a
f = \(MkT x1) (MkT x2) -> x1 + x2
But this won't:
f :: T a -> T a -> a
f = \t1 t2 ->
let aux (MkT x1) (MkT x2) = x1 + x2
in aux t1 t2
Bug.hs:10:25: error:
• Couldn't match expected type ‘p’ with actual type ‘T a0’
‘p’ is untouchable
inside the constraints: a1 ~ Int
bound by a pattern with constructor: MkT :: Int -> T Int,
in an equation for ‘aux’
at Bug.hs:10:16-21
‘p’ is a rigid type variable bound by
the inferred type of aux :: T a1 -> p -> p1
at Bug.hs:10:11-41
Possible fix: add a type signature for ‘aux’
• In the pattern: MkT x2
In an equation for ‘aux’: aux (MkT x1) (MkT x2) = x1 + x2
In the expression: let aux (MkT x1) (MkT x2) = x1 + x2 in aux t1 t2
• Relevant bindings include
aux :: T a1 -> p -> p1 (bound at Bug.hs:10:11)
|
10 | let aux (MkT x1) (MkT x2) = x1 + x2
| ^^^^^^
Bug.hs:10:35: error:
• Couldn't match expected type ‘p1’ with actual type ‘Int’
‘p1’ is untouchable
inside the constraints: a0 ~ Int
bound by a pattern with constructor: MkT :: Int -> T Int,
in an equation for ‘aux’
at Bug.hs:10:25-30
‘p1’ is a rigid type variable bound by
the inferred type of aux :: T a1 -> p -> p1
at Bug.hs:10:11-41
Possible fix: add a type signature for ‘aux’
• In the expression: x1 + x2
In an equation for ‘aux’: aux (MkT x1) (MkT x2) = x1 + x2
In the expression: let aux (MkT x1) (MkT x2) = x1 + x2 in aux t1 t2
• Relevant bindings include
aux :: T a1 -> p -> p1 (bound at Bug.hs:10:11)
|
10 | let aux (MkT x1) (MkT x2) = x1 + x2
| ^^^^^^^
I like the unlifted pair idea. Contrary to your assumption, using UnboxedTupE
does not require -XUnboxedTuples
. So that's good. It is true that you would need -fobject-code
on older GHCi's, but I think that's OK (perhaps you disagree). One remaining problem, though, is that you evidently need TupleSections
to use e.g. UnboxedTupE [Nothing, Nothing]
, which is a shame. I was able to get ConE
to work with an unboxed tuple constructor name, though, so that might make a way forward, if we had a way to generate those names programmatically -- I didn't do this last experiment.
Contrary to your assumption, using
UnboxedTupE
does not require-XUnboxedTuples
.
Doesn't it? If I try this:
{-# LANGUAGE TemplateHaskell #-}
module Foo where
import Language.Haskell.TH
f :: ()
f = let x = $(pure (UnboxedTupE [Just (LitE (CharL 'a')), Just (LitE (CharL 'b'))])) in ()
Then GHC rejects it without -XUnboxedTuples
:
$ ghci-9.2 Foo.hs
GHCi, version 9.2.2: https://www.haskell.org/ghc/ :? for help
Loaded GHCi configuration from /home/rscott/.ghci
[1 of 1] Compiling Foo ( Foo.hs, interpreted )
Foo.hs:7:9: error:
• Illegal unboxed tuple type as function argument: (# Char, Char #)
Perhaps you intended to use UnboxedTuples
• When checking the inferred type
x :: (# Char, Char #)
In the expression: let x = ((# 'a', 'b' #)) in ()
In an equation for ‘f’: f = let x = ((# 'a', 'b' #)) in ()
|
7 | f = let x = $(pure (UnboxedTupE [Just (LitE (CharL 'a')), Just (LitE (CharL 'b'))])) in ()
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
One remaining problem, though, is that you evidently need
TupleSections
to use e.g.UnboxedTupE [Nothing, Nothing]
, which is a shame.
This is true, but I don't think solution (1) would ever generate code involving tuple sections. All of the uses of UnboxedTupE
would be fully saturated, unless I'm overlooking something.
I've observed the same as you, but the key bit is that the error comes up when validating an inferred type for a variable, x
in your case. In the desugaring plan, no variable would have an unboxed-tuple type, so all is well.
Hah, a good observation. If you say case $(pure (UnboxedTupE [Just (LitE (CharL 'a')), Just (LitE (CharL 'b'))])) of ...
, for instance, then all is well. I suppose it's a bit fragile to rely on GHC not requiring UnboxedTuples
here, but so the cookie crumbles.
But now I understand what you were getting at with the talk of tuple sections. After all, there is no DExp
constructor for tuple syntax (boxed or unboxed), so in order to represent an application of an unboxed tuple to arguments, you'd have to do so like DConE <unboxed tuple data constructor> `DAppE` ... `DAppE` ...
. As you surmised in https://github.com/goldfirere/th-desugar/issues/158#issuecomment-1108949989, however, there is a way to retrieve unboxed tuple data constructor names programmatically: template-haskell
's unboxedTupleDataName
function. As such, I believe this would avoid needing TupleSection
as well.
In short: it's brittle, but it just might work.
In short: it's brittle, but it just might work.
Like the rest of th-desugar and singletons. Excellent. :)
It turns out that there actually is an example of desugared code that would require the use of the UnboxedTuples
extension, and it involves match flattening. In this example:
{-# LANGUAGE TemplateHaskell #-}
{-# OPTIONS_GHC -ddump-splices #-}
module Bug where
import Language.Haskell.TH
import Language.Haskell.TH.Desugar
data Pair a b = MkPair a b
$(pure [])
g :: String
g =
$(do e1 <- [| let f ~(MkPair x y) = x ++ y
in f (MkPair "a" "b")
|]
e2 <- dsExp e1
e3 <- scExp e2
pure (sweeten e3))
The desugared code is:
..\Bug.hs:(14,5)-(19,24): Splicing expression
do e1_aCde <- [| let
f_aCdb ~(MkPair x_aCdc y_aCdd) = x_aCdc ++ y_aCdd
in f_aCdb (MkPair "a" "b") |]
e2_aCdf <- dsExp e1_aCde
e3_aCdg <- scExp e2_aCdf
pure (sweeten e3_aCdg)
======>
let
f_aCeK _arg_6989586621679156781_aCeO
= let
tuple_6989586621679156789_aCeW
= case _arg_6989586621679156781_aCeO of {
MkPair _x_6989586621679156783_aCeQ _y_6989586621679156785_aCeS
-> let x_aCeL = _x_6989586621679156783_aCeQ in
let y_aCeM = _y_6989586621679156785_aCeS in ((#,#) x_aCeL) y_aCeM }
x_aCeL
= case tuple_6989586621679156789_aCeW of {
(#,#) proj_6989586621679156791_aCeY _
-> proj_6989586621679156791_aCeY }
y_aCeM
= case tuple_6989586621679156789_aCeW of {
(#,#) _ proj_6989586621679156793_aCf0
-> proj_6989586621679156793_aCf0 }
in ((++) x_aCeL) y_aCeM
in f_aCeK ((MkPair "a") "b")
Notice that tuple_6989586621679156789_aCeW
returns an unboxed tuple. As a result, GHC infers an unboxed tuple type for it, and it complains as such:
..\Bug.hs:14:5: error:
• Illegal unboxed tuple type as function argument: (# a0, b0 #)
Perhaps you intended to use UnboxedTuples
• When checking the inferred type
tuple_6989586621679156789_aCeW :: (# a0, b0 #)
In the expression:
let
tuple_6989586621679156789_aCeW
= case _arg_6989586621679156781_aCeO of {
MkPair _x_6989586621679156783_aCeQ _y_6989586621679156785_aCeS
-> ... }
x_aCeL
= case tuple_6989586621679156789_aCeW of {
(#,#) proj_6989586621679156791_aCeY _ -> ... }
y_aCeM
= case tuple_6989586621679156789_aCeW of {
(#,#) _ proj_6989586621679156793_aCf0 -> ... }
in ((++) x_aCeL) y_aCeM
In an equation for ‘f_aCeK’:
f_aCeK _arg_6989586621679156781_aCeO
= let
tuple_6989586621679156789_aCeW = ...
x_aCeL = ...
....
in ((++) x_aCeL) y_aCeM
|
14 | $(do e1 <- [| let f ~(MkPair x y) = x ++ y
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^...
The relevant code is in mkSelectorDecs
. Interestingly, the comments for that function claim that the code is similar to what GHC itself does during compilation. I wonder how it handles this example?
Ah, I figured out the answer to my own question. Here is a variant of that example which I cooked up to determine how mkSelectorDecs
behaves for unlifted types:
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE KindSignatures #-}
{-# OPTIONS_GHC -ddump-simpl #-}
module Bug where
import GHC.Exts
data Pair (a :: TYPE UnliftedRep) (b :: TYPE UnliftedRep) = MkPair a b
f :: Pair a b -> Pair b a
f ~(MkPair x y) = MkPair y x
The answer is surprisingly straightforward: it just chooses not to even try:
$ ghc Bug.hs
[1 of 1] Compiling Bug ( Bug.hs, Bug.o )
Bug.hs:11:4: error:
A lazy (~) pattern cannot bind variables of unlifted type.
Unlifted variables:
x :: a
y :: b
|
11 | f ~(MkPair x y) = MkPair y x
| ^^^^^^^^^^^^
In that case, I'll apply the same restriction to th-desugar
's implementation of mkSelectorDecs
as well.
I've pushed a fix to the T158
branch, and I've also verified that this does not regress singletons
. (That is unsurprising, since singletons
has very special treatment of unboxed tuples, but it never hurts to check.)
That being said, there is still one small snag that I've run into: this approach doesn't work on GHC 7.10, and only on 7.10. (It even works on 7.8, to make things more annoying.) Here is an excerpt from the CI for the 7.10 job:
[1 of 8] Compiling T158Exp ( Test/T158Exp.hs, /__w/th-desugar/th-desugar/dist-newstyle/build/x86_64-linux/ghc-7.10.3/th-desugar-1.13/t/spec/build/spec/spec-tmp/T158Exp.o )
Test/T158Exp.hs:14:5:
Illegal data constructor name: ‘(#,#)’
When splicing a TH expression:
(\arg_1627423212_0 arg_1627423214_1 -> case GHC.Tuple.(#,#) arg_1627423212_0 arg_1627423214_1 of
GHC.Tuple.(#,#) 27# 42# -> GHC.Tuple.()) 27# 42#
In the splice:
$([| (\ 27# 42# -> ()) 27# 42# |] >>= dsExp >>= return . expToTH)
I wonder if we should just drop support for pre-8.0 versions of GHC at this point.
I think dropping support for pre-8.0 is indeed reasonable. Maybe note in the README that if someone finds this problematic, they should submit a bug report.
Sounds good to me. I've opened #163 to make GHC 8.0 the minimum. Once that lands, I can implement a fix for this issue without remorse.
This
th-desugar
example involving unlifted types typechecks:However, if I change
f
to use a lambda expression:Then the generated code no longer typechecks:
As the
-ddump-splices
output indicates, this is because we desugar\27# 42# -> ()
to\arg1 arg2 -> case (arg1, arg2) of (27#, 42#) -> ()
. This uses a lifted pair(,)
, however, which won't work for unlifted types likeInt#
.I can think of a couple of solutions to this problem:
(#,#)
. This would require desugared code to enableUnboxedTuples
in surprising places, however. Moreover, the desugared code would not work in old versions of GHCi without enabling-fobject-code
.Instead of using a tuple, instead
let
-bind an auxiliary function which performs the match:This avoids the use of tuples altogether. I'm not yet clear if this would have ramifications for
singletons
, however.