Template Haskell's handling of infix GADT constructors is broken
There are several infelicities in the way that Template Haskell treats GADT constructors that are declared to be infix. To illustrate:
{-# LANGUAGE GADTs #-}
{-# LANGUAGE TemplateHaskell #-}
module Main (main) where
import Language.Haskell.TH
infixr 7 :***:
data GADT a where
Prefix :: Int -> Int -> GADT Int
(:***:) :: Int -> Int -> GADT Int
$(return [])
main :: IO ()
main = do
putStrLn $(reify ''GADT >>= stringE . pprint)
putStrLn ""
putStrLn $(reify ''GADT >>= stringE . show)
This doesn't print out quite what you'd expect:
data Main.GADT (a_0 :: *)
= Main.Prefix :: GHC.Types.Int ->
GHC.Types.Int -> Main.GADT GHC.Types.Int
| GHC.Types.Int Main.:***: GHC.Types.Int
TyConI (DataD [] Main.GADT [KindedTV a_1627394505 StarT] Nothing [GadtC [Main.Prefix] [(Bang NoSourceUnpackedness NoSourceStrictness,ConT GHC.Types.Int),(Bang NoSourceUnpackedness NoSourceStrictness,ConT GHC.Types.Int)] Main.GADT [ConT GHC.Types.Int],InfixC (Bang NoSourceUnpackedness NoSourceStrictness,ConT GHC.Types.Int) Main.:***: (Bang NoSourceUnpackedness NoSourceStrictness,ConT GHC.Types.Int)] [])
TH thinks that GADT
is a Haskell98 data declaration when pprint
-ing it because (:***:)
is converted to an InfixC
(see here for the relevant code). This causes the output to be a strange hodgepodge of Haskell98 and GADT syntax.
Another issue is that even though I can reify GADT
, I can't splice it back in! Compiling this:
{-# LANGUAGE GADTs #-}
{-# LANGUAGE TemplateHaskell #-}
module Main (main) where
import Language.Haskell.TH
$(do gadtName <- newName "GADT"
prefixName <- newName "Prefix"
infixName <- newName ":***:"
a <- newName "a"
return [DataD [] gadtName [KindedTV a StarT] Nothing [GadtC [prefixName] [(Bang NoSourceUnpackedness NoSourceStrictness,ConT ''Int),(Bang NoSourceUnpackedness NoSourceStrictness,ConT ''Int)] gadtName [ConT ''Int],InfixC (Bang NoSourceUnpackedness NoSourceStrictness,ConT ''Int) infixName (Bang NoSourceUnpackedness NoSourceStrictness,ConT ''Int)] []])
$(return [])
main :: IO ()
main = do
putStrLn $(reify ''GADT >>= stringE . pprint)
putStrLn ""
putStrLn $(reify ''GADT >>= stringE . show)
Results in an error:
InfixGADT.hs:12:3: error:
Cannot mix GADT constructors with Haskell 98 constructors
When splicing a TH declaration:
data GADT_0 (a_1 :: *)
= Prefix_2 :: GHC.Types.Int ->
GHC.Types.Int -> GADT_0 GHC.Types.Int
| GHC.Types.Int :***:_3 GHC.Types.Int
This code is responsible. We have an issue where InfixC
can be either Haskell98 or GADT syntax depending on the context, but in that particular context, there's not a good way to determine it.
I can think of three solutions:
- Add an
InfixGadtC
constructor. This adds more clutter toCon
, but is the most straightforward fix. - Subsume infix GADT constructors under
GadtC
/RecGadtC
(depending on if it has records), and treatInfixC
as always being Haskell98. This wouldn't require any API changes, but it does leave a bit of asymmetry between the Haskell98 and GADT constructors, since there would be three of the former but two of the latter. - A radical approach (which subsumes option 2) would be to deprecate
InfixC
, subsume it underNormalC
/RecC
, and add anInfixC
pattern synonym for compatibility.InfixC
does seem extraneous anyway since you can just usereifyFixity
to determine if a constructor is infix. That way, you have two Haskell98 and two GADT constructors (but you'd also have to deprecateInfixC
).
Trac metadata
Trac field | Value |
---|---|
Version | 8.1 |
Type | Bug |
TypeOfFailure | OtherFailure |
Priority | normal |
Resolution | Unresolved |
Component | Template Haskell |
Test case | |
Differential revisions | |
BlockedBy | |
Related | |
Blocking | |
CC | goldfire, jstolarek |
Operating system | |
Architecture |