th-desugar
Functions to desugar Template Haskell
https://github.com/goldfirere/th-desugar
| Version on this page: | 1.10 |
| LTS Haskell 24.18: | 1.17 |
| Stackage Nightly 2025-11-04: | 1.18 |
| Latest on Hackage: | 1.18 |
th-desugar-1.10@sha256:cf7debc3f197c5d73f390e61a8be7b4853c8bf3c11e52e3da1338c3eb3af31d2,3464Module documentation for 1.10
- Language
- Language.Haskell
th-desugar Package
This package provides the Language.Haskell.TH.Desugar module, which desugars
Template Haskell’s rich encoding of Haskell syntax into a simpler encoding.
This desugaring discards surface syntax information (such as the use of infix
operators) but retains the original meaning of the TH code. The intended use
of this package is as a preprocessor for more advanced code manipulation
tools. Note that the input to any of the ds... functions should be produced
from a TH quote, using the syntax [| ... |]. If the input to these functions
is a hand-coded TH syntax tree, the results may be unpredictable. In
particular, it is likely that promoted datatypes will not work as expected.
One explicit goal of this package is to reduce the burden of supporting multiple GHC / TH versions. Thus, the desugared language is the same across all GHC versions, and any inconsistencies are handled internally.
The package was designed for use with the singletons package, so some design
decisions are based on that use case, when more than one design choice was
possible.
I will try to keep this package up-to-date with respect to changes in GHC.
Known limitations
th-desugar sometimes has to construct types for certain Haskell entities.
For instance, th-desugar desugars all Haskell98-style constructors to use
GADT syntax, so the following:
data T (a :: k) = MkT (Proxy a)
Will be desugared to something like this:
data T (a :: k) where
MkT :: forall k (a :: k). Proxy a -> T (a :: k)
Notice that k is explicitly quantified in the type of MkT. This is due to
an additional pass that th-desugar performs over the type variable binders
of T to extract all implicitly quantified variables and make them explicit.
This makes the desugared types forwards-compatible with a
future version of GHC
that requires all kind variables in a top-level forall to be explicitly
quantified.
This process of extracting all implicitly quantified kind variables is not
perfect, however. There are some obscure programs that will cause th-desugar
to produce type variable binders that are ill scoped. Here is one example:
data P k (a :: k)
data Foo (a :: Proxy j) (b :: k) c = MkFoo c (P k j)
If you squint hard at MkFoo, you’ll notice that j :: k. However, this
relationship is not expressed syntactically, which means that th-desugar
will not be aware of it. Therefore, th-desugar will desugar Foo to:
data Foo (a :: Proxy j) (b :: k) c where
MkFoo :: forall j k (a :: Proxy j) (b :: k) c.
c -> P k j -> Foo (a :: Proxy j) (b :: k) c
This is incorrect since k must come before j in order to be well scoped.
There is a workaround to this issue, however: add more explicit kind
information. If you had instead written this:
data Foo (a :: Proxy (j :: k)) (b :: k) c = MkFoo c (P k j)
Then the fact that j :: k is expressed directly in the AST, so th-desugar
is able to pick up on it and pick forall k j (a :: Proxy j) (b :: k) c. <...>
as the telescope for the type of MkFoo.
The following constructs are known to be susceptible to this issue:
- Desugared Haskell98-style constructors
- Locally reified class methods
- Locally reified record selectors
- Locally reified data constructors
- Locally reified type family instances (on GHC 8.8 and later, in which the
Template Haskell AST supports explicit
forallsin type family equations)
Changes
th-desugar release notes
Version 1.10
-
Support GHC 8.8. Drop support for GHC 7.6.
-
Add support for visible kind application, type variable
forallsinRULES, and explicitforalls in type family instances. Correspondingly,-
There is now a
DAppKindTconstructor inDType. -
Previously, the
DDataInstDconstructor had fields of typeNameand[DType]. Those have been scrapped in favor of a single field of typeDType, representing the application of the data family name (which was previously theName) to its arguments (which was previously the[DType]).DDataInstDalso has a new field of typeMaybe [DTyVarBndr]to represent its explicitly quantified type variables (if present). -
Previously, the
DTySynEqnconstructor had a field of type[DType]. That has been scrapped in favor of a field of typeDType, representing the application of the type family name (whichDTySynEqndid not used to contain!) to its arguments (which was previously the[DType]).DTySynEqnalso has a new field of typeMaybe [DTyVarBndr]to represent its explicitly quantified type variables (if present). -
DTySynInstDno longer has a field of typeName, as that is redundant now that eachDTySynEqncontains the sameName. -
There is now a field of type
Maybe [DTyVarBndr]in theDRulePconstructor to represent bound type variables inRULES(if present).
-
-
Add a field of type
Maybe [DTyVarBndr]toDInstanceDandDStandaloneDerivDfor optionally quantifying type variables explicitly. If supplied with aJust, this sweetens the instance type to use aForallTto represent the explicit quantification. This trick is not supported forInstanceDon GHC 8.0 and forStandaloneDerivDon GHC 7.10 or 8.0, so be aware of this limitation if you supplyJustfor this field. -
Add support for desugaring implicit params. This does not involve any changes to the
th-desugarAST, as:(?x :: a) => ...is desugared toIP "x" a => ....id ?xis desugared toid (ip @"x").let ?x = 42 in ...is desugared tolet new_x_val = 42 in bindIP @"x" new_x_val ...(wherebindIPis a new utility function exported byLanguage.Haskell.TH.Desugaron GHC 8.0 or later).
In order to support this desugaring, the type signatures of
dsLetDecanddsLetDecsnow return([DLetDec], DExp -> DExp)instead of just[DLetDec], whereDExp -> DExpis the expression which binds the values of implicit params (e.g.,\z -> bindIP @"x" new_x_val z) if any are bound. (If none are bound, this is simply theidfunction.) -
Fix a bug in which
toposortTyVarsOfwould error at runtime if given types containingforalls as arguments. -
Fix a bug in which
fvDTypewould return incorrect results if given a type containing quantified constraints. -
Fix a bug in which
expandTypewould not expand type synonyms in the kinds of type variable binders inforalls. -
Fix a bug in which
getRecordSelectorswould omit record selectors from GADT constructors. -
Fix a bug in which
toposortTyVarsOfwould sometimes not preserve the left-to-right ordering ofNames generated withqNewName. -
Locally reified class methods, data constructors, and record selectors now quantify kind variables properly.
-
Desugared ADT constructors now quantify kind variables properly.
-
Remove
DPred, as it has become too similar toDType. This also means that theDPatconstructors, which previously ended with the suffixPa, can now use the suffixP, mirroring TH. -
The type of
applyDTypehas changed fromDType -> [DType] -> DTypetoDType -> [DTypeArg] -> DType, whereDTypeArgis a new data type that encodes whether an argument is a normal type argument (e.g., theIntinMaybe Int) or a visible kind argument (e.g., the@TypeinProxy @Type Char). ATypeArgdata type (which is likeDTypeArg, but withTypes/Kinds instead ofDTypes/DKinds) is also provided.A handful of utility functions for manipulating
TypeArgs andDTypeArgs are also exported. -
th-desugarfunctions that compute free variables (e.g.,fvDType) now return anOSet, a variant ofSetthat remembers the order in which elements were inserted. A consequence of this change is that it fixes a bug that causes free variables to be computed in different orders depending on which unique numbers GHC happened to generate internally. -
Substition and type synonym expansion are now more efficient by avoiding the use of
sybin inner loops.
Version 1.9
-
Suppose GHC 8.6.
-
Add support for
DerivingVia. Correspondingly, there is now aDDerivStrategydata type. -
Add support for
QuantifiedConstraints. Correspondingly, there is now aDForallPrconstructor inDPredto represent quantified constraint types. -
Remove the
DStarTconstructor ofDTypein favor ofDConT ''Type. Two utility functions have been added toLanguage.Haskell.TH.Desugarto ease this transition:isTypeKindName: returnsTrueif the argumentNameis that ofTypeor★(or*, to support older GHCs).typeKindName: the name ofType(on GHC 8.0 or later) or*(on older GHCs).
-
th-desugarnow desugars all data types to GADT syntax. The most significant API-facing changes resulting from this new design are:-
The
DDataD,DDataFamilyD, andDDataFamInstDconstructors ofDDecnow haveMaybe DKindfields that either haveJustan explicit return kind (e.g., thek -> Type -> Typeindata Foo :: k -> Type -> Type) orNothing(if lacking an explicit return kind). -
The
DConconstructor previously had a field of typeMaybe DType, since there was a possibility it could be a GADT (with an explicit return type) or non-GADT (without an explicit return type) constructor. Since all data types are desugared to GADTs now, this field has been changed to be simply aDType. -
The type signature of
dsConwas previously:dsCon :: DsMonad q => Con -> q [DCon]However, desugaring constructors now needs more information than before, since GADT constructors have richer type signatures. Accordingly, the type of
dsConis now:dsCon :: DsMonad q => [DTyVarBndr] -- ^ The universally quantified type variables -- (used if desugaring a non-GADT constructor) -> DType -- ^ The original data declaration's type -- (used if desugaring a non-GADT constructor). -> Con -> q [DCon]The
instance Desugar [Con] [DCon]has also been removed, as the previous implementation ofdesugar(concatMapM dsCon) no longer has enough information to work.
Some other utility functions have also been added as part of this change:
-
A
conExistentialTvbsfunction has been introduced to determine the existentially quantified type variables of aDCon. Note that this function is not 100% accurate—refer to the documentation forconExistentialTvbsfor more information. -
A
mkExtraDKindBindersfunction has been introduced to turn a data type’s return kind into explicit, fresh type variable binders. -
A
toposortTyVarsOffunction, which finds the free variables of a list ofDTypes and returns them in a well scoped list that has been sorted in reverse topological order.
-
-
th-desugarnow desugars partial pattern matches indo-notation and list/monad comprehensions to the appropriate invocation offail. (Previously, these were incorrectly desugared intocaseexpressions with incomplete patterns.) -
Add a
mkDLamEFromDPatsfunction for constructing aDLamEexpression using a list ofDPatarguments and aDExpbody. -
Add an
unravelfunction for decomposing a function type into itsforall’d type variables, its context, its argument types, and its result type. -
Export a
substTyVarBndrsfunction fromLanguage.Haskell.TH.Desugar.Subst, which substitutes over type variable binders in a capture-avoiding fashion. -
getDataD,dataConNameToDataName, anddataConNameToConfromLanguage.Haskell.TH.Desugar.Reifynow look up local declarations. As a result, the contexts in their type signatures have been strengthened fromQuasitoDsMonad. -
Export a
dTyVarBndrToDTypefunction which converts aDTyVarBndrto aDType, which preserves its kind. -
Previously,
th-desugarwould silently accept illegal uses of record construction with fields that did not belong to the constructor, such asIdentity { notAField = "wat" }. This is now an error.
Version 1.8
-
Support GHC 8.4.
-
substTynow properly substitutes into kind signatures. -
Expose
fvDType, which computes the free variables of aDType. -
Incorporate a
DDeclaredInfixfield intoDNormalCto indicate if it is a constructor that was declared infix. -
Implement
lookupValueNameWithLocals,lookupTypeNameWithLocals,mkDataNameWithLocals, andmkTypeNameWithLocals, counterparts tolookupValueName,lookupTypeName,mkDataName, andmkTypeNamewhich have access to local Template Haskell declarations. -
Implement
reifyNameSpaceto determine aName’sNameSpace. -
Export
reifyFixityWithLocalsfromLanguage.Haskell.TH.Desugar. -
Export
matchTy(among other goodies) from new moduleLanguage.Haskell.TH.Subst. This function matches a type template against a target.
Version 1.7
-
Support for TH’s support for
TypeApplications, thanks to @RyanGlScott. -
Support for unboxed sums, thanks to @RyanGlScott.
-
Support for
COMPLETEpragmas. -
getRecordSelectorsnow requires a list ofDCons as an argument. This makes it easier to return correct record selector bindings in the event that a record selector appears in multiple constructors. (See goldfirere/singletons#180 for an example of where the old behavior ofgetRecordSelectorswent wrong.) -
Better type family expansion (expanding an open type family with variables works now).
Version 1.6
-
Work with GHC 8, with thanks to @christiaanb for getting this change going. This means that several core datatypes have changed: partcularly, we now have
DTypeFamilyHeadand fixities are now reified separately from other things. -
DKindis merged withDType. -
Genericinstances for everything.
Version 1.5.5
- Fix issue #34. This means that desugaring (twice) is idempotent over expressions, after the second time. That is, if you desugar an expression, sweeten it, desugar again, sweeten again, and then desugar a third time, you get the same result as when you desugared the second time. (The extra round-trip is necessary there to make the output smaller in certain common cases.)
Version 1.5.4.1
- Fix issue #32, concerning reification of classes with default methods.
Version 1.5.4
- Added
expandUnsoundly
Version 1.5.3
- More
DsMonadinstances, thanks to David Fox.
Version 1.5.2
- Sweeten kinds more, too.
Version 1.5.1
-
Thanks to David Fox (@ddssff), sweetening now tries to use more of TH’s
Typeconstructors. -
Also thanks to David Fox, depend usefully on the th-orphans package.
Version 1.5
-
There is now a facility to register a list of
Decthat internal reification should use when necessary. This avoids the user needing to break up their definition across different top-level splices. SeewithLocalDeclarations. This has a side effect of changing theQuasitypeclass constraint on many functions to be the newDsMonadconstraint. Happily, there areDsMonadinstances forQandIO, the two normal inhabitants ofQuasi. -
“Match flattening” is implemented! The functions
scExpandscLetDecremove any nested pattern matches. -
More is now exported from
Language.Haskell.TH.Desugarfor ease of use. -
expandcan now expand closed type families! It still requires that the type to expand contain no type variables. -
Support for standalone-deriving and default signatures in GHC 7.10. This means that there are now two new constructors for
DDec. -
Support for
staticexpressions, which are new in GHC 7.10.
Version 1.4.2
expandfunctions now consider open type families, as long as the type to be expanded has no free variables.
Version 1.4.1
-
Added
Language.Haskell.TH.Desugar.Lift, which providesLiftinstances for all of the th-desugar types, as well as several Template Haskell types. -
Added
applyDExpandapplyDTypeas convenience functions.
Version 1.4.0
-
All
Decs can now be desugared, to the newDDectype. -
Sweetening
Decs that do not exist in GHC 7.6.3- works on a “best effort” basis: closed type families are sweetened to open ones, and role annotations are dropped. -
Infos can now be desugared. Desugaring takes into account GHC bug #8884, which meant that reifying poly-kinded type families in GHC 7.6.3- was subtly wrong. -
There is a new function
flattenDValDwhich takes a binding likelet (a,b) = fooand breaks it apart into separate assignments foraandb. -
There is a new
Desugarclass with methodsdesugarandsweeten. See the documentation inLanguage.Haskell.TH.Desugar. -
Variable names that are distinct in desugared code are now guaranteed to have distinct answers to
nameBase. -
Added a new function
getRecordSelectorsthat extracts types and definitions of record selectors from a datatype definition.
Version 1.3.1
- Update cabal file to include testing files in sdist.
Version 1.3.0
- Update to work with
type Pred = Typein GHC 7.9. This changed theDPredtype for all GHC versions, though.
Version 1.2.0
- Generalized interface to allow any member of the
Qausiclass, instead of justQ.
Version 1.1.1
- Made compatible with HEAD after change in role annotation syntax.
Version 1.1
- Added module
Language.Haskell.TH.Desugar.Expand, which allows for expansion of type synonyms in desugared types. - Added
Show,Typeable, andDatainstances to desugared types. - Fixed bug where an as-pattern in a
letstatement was scoped incorrectly. - Changed signature of
dsPatto be more specific to as-patterns; this allowed for fixing theletscoping bug. - Created new functions
dsPatOverExpanddsPatsOverExpto allow for easy desugaring of patterns. - Changed signature of
dsLetDecto return a list ofDLetDecs. - Added
dsLetDecsfor convenience. Now, instead of usingmapM dsLetDec, you should usedsLetDecs.
Version 1.0
- Initial release