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
InfixGadtCconstructor. 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 treatInfixCas 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 anInfixCpattern synonym for compatibility.InfixCdoes seem extraneous anyway since you can just usereifyFixityto 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 |