-
Notifications
You must be signed in to change notification settings - Fork 37
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
Add promotedDataTypeName option to make working with arrows/literals easier #450
Comments
If we're going to add newtype Age' n = MkAge' n
type Age = Age' Natural
type PAge = Age' Nat
type MkAge = MkAge' :: Natural -> Age
type PMkAge = MkAge' :: Nat -> Age If so, then they could override |
After some further thought, I'm less enthusiastic about this idea than I once was. The main reason is that actually implementing this idea could lead to slower compile times, even for code that doesn't make use of singletons/src/Data/Singletons/Promote/Type.hs Lines 60 to 67 in 46d0d74
This is definitely a place where we'd need to hook I'm very reluctant to make $(promote [d|
data Foo = ...
f :: Foo -> Foo -> Foo
f = ...
... In order to ascertain the There are some ways we could mitigate this problem, but I'm not particularly enthusiastic about them either:
|
We could, er, reify this design pattern by giving users a way to say that |
Ah, that is a design I hadn't considered. I'll try that and see if there are any complications. |
After some brief experimentation, I realized that the design in #450 (comment) is actually not that different from option (1) in #450 (comment). In fact, I was surprised to find that option (1) was the least clunky to use in practice, so despite my prior lack of enthusiasm for it, I think that is the best way forward. Here is a WIP patch that implements a diff --git a/src/Data/Singletons/Promote.hs b/src/Data/Singletons/Promote.hs
index 06efc97..d9022a8 100644
--- a/src/Data/Singletons/Promote.hs
+++ b/src/Data/Singletons/Promote.hs
@@ -1021,13 +1021,10 @@ promotePat (DVarP name) = do
tell $ PromDPatInfos [(name, tyName)] OSet.empty
return (DVarT tyName, ADVarP name)
promotePat (DConP name pats) = do
+ opts <- getOptions
(types, pats') <- mapAndUnzipM promotePat pats
- let name' = unboxed_tuple_to_tuple name
+ let name' = promotedDataTypeOrConName opts name
return (foldType (DConT name') types, ADConP name pats')
- where
- unboxed_tuple_to_tuple n
- | Just deg <- unboxedTupleNameDegree_maybe n = tupleDataName deg
- | otherwise = n
promotePat (DTildeP pat) = do
qReportWarning "Lazy pattern converted into regular pattern in promotion"
second ADTildeP <$> promotePat pat
diff --git a/src/Data/Singletons/Promote/Defun.hs b/src/Data/Singletons/Promote/Defun.hs
index dcbb41a..d6fa7ca 100644
--- a/src/Data/Singletons/Promote/Defun.hs
+++ b/src/Data/Singletons/Promote/Defun.hs
@@ -146,12 +146,14 @@ buildDefunSymsDataD ctors =
where
promoteCtor :: DCon -> PrM [DDec]
promoteCtor (DCon tvbs _ name fields res_ty) = do
- let arg_tys = tysOfConFields fields
+ opts <- getOptions
+ let name' = promotedDataTypeOrConName opts name
+ arg_tys = tysOfConFields fields
arg_kis <- traverse promoteType_NC arg_tys
res_ki <- promoteType_NC res_ty
let con_ki = ravelVanillaDType tvbs [] arg_kis res_ki
- m_fixity <- reifyFixityWithLocals name
- defunctionalize name m_fixity $ DefunSAK con_ki
+ m_fixity <- reifyFixityWithLocals name'
+ defunctionalize name' m_fixity $ DefunSAK con_ki
-- Generate defunctionalization symbols for a name, using reifyFixityWithLocals
-- to determine what the fixity of each symbol should be
diff --git a/src/Data/Singletons/Promote/Type.hs b/src/Data/Singletons/Promote/Type.hs
index 23eae12..5cad645 100644
--- a/src/Data/Singletons/Promote/Type.hs
+++ b/src/Data/Singletons/Promote/Type.hs
@@ -13,14 +13,11 @@ module Data.Singletons.Promote.Type
import Language.Haskell.TH.Desugar
import Data.Singletons.Names
+import Data.Singletons.TH.Options
import Data.Singletons.Util
-import Language.Haskell.TH
-- Promote a DType to the kind level.
---
--- NB: the only monadic thing we do here is fail. This allows the function
--- to be used from the Singletons module.
-promoteType :: MonadFail m => DType -> m DKind
+promoteType :: OptionsMonad m => DType -> m DKind
promoteType ty = do
checkVanillaDType ty
promoteType_NC ty
@@ -28,10 +25,10 @@ promoteType ty = do
-- Promote a DType to the kind level. This is suffixed with "_NC" because
-- we do not invoke checkVanillaDType here.
-- See [Vanilla-type validity checking during promotion].
-promoteType_NC :: MonadFail m => DType -> m DKind
+promoteType_NC :: OptionsMonad m => DType -> m DKind
promoteType_NC = go []
where
- go :: MonadFail m => [DTypeArg] -> DType -> m DKind
+ go :: OptionsMonad m => [DTypeArg] -> DType -> m DKind
go [] (DForallT fvf tvbs ty) = do
ty' <- go [] ty
pure $ DForallT fvf tvbs ty'
@@ -57,14 +54,9 @@ promoteType_NC = go []
-- No need to promote 'ki' - it is already a kind.
return $ applyDType (DSigT ty' ki) args
go args (DVarT name) = return $ applyDType (DVarT name) args
- go [] (DConT name)
- | name == typeRepName = return $ DConT typeKindName
- | nameBase name == nameBase repName = return $ DConT typeKindName
- go args (DConT name)
- | Just n <- unboxedTupleNameDegree_maybe name
- = return $ applyDType (DConT (tupleTypeName n)) args
- | otherwise
- = return $ applyDType (DConT name) args
+ go args (DConT name) = do
+ opts <- getOptions
+ return $ applyDType (DConT (promotedDataTypeOrConName opts name)) args
go [DTANormal k1, DTANormal k2] DArrowT
= return $ DConT tyFunArrowName `DAppT` k1 `DAppT` k2
go _ ty@DLitT{} = pure ty
@@ -76,13 +68,13 @@ promoteType_NC = go []
-- | Promote a DTypeArg to the kind level. This is suffixed with "_NC" because
-- we do not invoke checkVanillaDType here.
-- See [Vanilla-type validity checking during promotion].
-promoteTypeArg_NC :: MonadFail m => DTypeArg -> m DTypeArg
+promoteTypeArg_NC :: OptionsMonad m => DTypeArg -> m DTypeArg
promoteTypeArg_NC (DTANormal t) = DTANormal <$> promoteType_NC t
promoteTypeArg_NC ta@(DTyArg _) = pure ta -- Kinds are already promoted
-- | Promote a DType to the kind level, splitting it into its type variable
-- binders, argument types, and result type in the process.
-promoteUnraveled :: MonadFail m
+promoteUnraveled :: OptionsMonad m
=> DType -> m ([DTyVarBndr], [DKind], DKind)
promoteUnraveled ty = do
(tvbs, _, arg_tys, res_ty) <- unravelVanillaDType ty
diff --git a/src/Data/Singletons/Single/Data.hs b/src/Data/Singletons/Single/Data.hs
index bad5cf6..a2f2cc9 100644
--- a/src/Data/Singletons/Single/Data.hs
+++ b/src/Data/Singletons/Single/Data.hs
@@ -170,7 +170,7 @@ singCtor dataName (DCon con_tvbs cxt name fields rty)
let types = tysOfConFields fields
sName = singledDataConName opts name
sCon = DConE sName
- pCon = DConT name
+ pCon = DConT $ promotedDataTypeOrConName opts name
checkVanillaDType $ ravelVanillaDType con_tvbs [] types rty
indexNames <- mapM (const $ qNewName "n") types
kinds <- mapM promoteType_NC types
diff --git a/src/Data/Singletons/TH/Options.hs b/src/Data/Singletons/TH/Options.hs
index de410c9..2daf274 100644
--- a/src/Data/Singletons/TH/Options.hs
+++ b/src/Data/Singletons/TH/Options.hs
@@ -22,6 +22,7 @@ module Data.Singletons.TH.Options
-- ** Options record selectors
, genQuotedDecs
, genSingKindInsts
+ , promotedDataTypeOrConName
, promotedClassName
, promotedValueName
, singledDataTypeName
@@ -45,7 +46,7 @@ import Control.Monad.RWS (RWST)
import Control.Monad.State (StateT)
import Control.Monad.Trans.Class (MonadTrans(..))
import Control.Monad.Writer (WriterT)
-import Data.Singletons.Names (consName, listName, nilName, splitUnderscores)
+import Data.Singletons.Names
import Data.Singletons.Util
import Language.Haskell.TH.Desugar
import Language.Haskell.TH.Syntax hiding (Lift(..))
@@ -53,19 +54,21 @@ import Language.Haskell.TH.Syntax hiding (Lift(..))
-- | Options that control the finer details of how @singletons@' Template
-- Haskell machinery works.
data Options = Options
- { genQuotedDecs :: Bool
+ { genQuotedDecs :: Bool
-- ^ If 'True', then quoted declarations will be generated alongside their
-- promoted and singled counterparts. If 'False', then quoted
-- declarations will be discarded.
- , genSingKindInsts :: Bool
+ , genSingKindInsts :: Bool
-- ^ If 'True', then 'SingKind' instances will be generated. If 'False',
-- they will be omitted entirely. This can be useful in scenarios where
-- TH-generated 'SingKind' instances do not typecheck (for instance,
-- when generating singletons for GADTs).
- , promotedClassName :: Name -> Name
+ , promotedDataTypeOrConName :: Name -> Name
+ -- ^ TODO RGS: Docs
+ , promotedClassName :: Name -> Name
-- ^ Given the name of the original, unrefined class, produces the name of
-- the promoted equivalent of the class.
- , promotedValueName :: Name -> Maybe Uniq -> Name
+ , promotedValueName :: Name -> Maybe Uniq -> Name
-- ^ Given the name of the original, unrefined value, produces the name of
-- the promoted equivalent of the value. This is used for both top-level
-- and @let@-bound names, and the difference is encoded in the
@@ -74,16 +77,16 @@ data Options = Options
-- @Just uniq@, where @uniq@ is a globally unique number that can be used
-- to distinguish the name from other local definitions of the same name
-- (e.g., if two functions both use @let x = ... in x@).
- , singledDataTypeName :: Name -> Name
+ , singledDataTypeName :: Name -> Name
-- ^ Given the name of the original, unrefined data type, produces the name
-- of the corresponding singleton type.
- , singledClassName :: Name -> Name
+ , singledClassName :: Name -> Name
-- ^ Given the name of the original, unrefined class, produces the name of
-- the singled equivalent of the class.
- , singledDataConName :: Name -> Name
+ , singledDataConName :: Name -> Name
-- ^ Given the name of the original, unrefined data constructor, produces
-- the name of the corresponding singleton data constructor.
- , singledValueName :: Name -> Name
+ , singledValueName :: Name -> Name
-- ^ Given the name of the original, unrefined value, produces the name of
-- the singled equivalent of the value.
, defunctionalizedName :: Name -> Int -> Name
@@ -113,15 +116,16 @@ data Options = Options
-- @<https://github.com/goldfirere/singletons/blob/master/README.md README>@.
defaultOptions :: Options
defaultOptions = Options
- { genQuotedDecs = True
- , genSingKindInsts = True
- , promotedClassName = promoteClassName
- , promotedValueName = promoteValNameLhs
- , singledDataTypeName = singTyConName
- , singledClassName = singClassName
- , singledDataConName = singDataConName
- , singledValueName = singValName
- , defunctionalizedName = promoteTySym
+ { genQuotedDecs = True
+ , genSingKindInsts = True
+ , promotedDataTypeOrConName = promoteDataTypeOrConName
+ , promotedClassName = promoteClassName
+ , promotedValueName = promoteValNameLhs
+ , singledDataTypeName = singTyConName
+ , singledClassName = singClassName
+ , singledDataConName = singDataConName
+ , singledValueName = singValName
+ , defunctionalizedName = promoteTySym
}
-- | Given the name of the original, unrefined, top-level value, produces the
@@ -235,6 +239,20 @@ promoteTySym name sat
promoteClassName :: Name -> Name
promoteClassName = prefixName "P" "#"
+-- TODO RGS: Docs
+promoteDataTypeOrConName :: Name -> Name
+promoteDataTypeOrConName nm
+ | nm == typeRepName = typeKindName
+ | nameBase nm == nameBase repName = typeKindName
+ | Just degree <- unboxedTupleNameDegree_maybe nm
+ = if isDataName nm then tupleDataName degree else tupleTypeName degree
+ | otherwise = nm
+ where
+ -- Is this name a data constructor name? A 'False' answer means "unsure".
+ isDataName :: Name -> Bool
+ isDataName (Name _ (NameG DataName _ _)) = True
+ isDataName _ = False
+
-- Singletons
singDataConName :: Name -> Name Now, for some good and bad news. I'll start with the bad news: the design I put forth in #450 (comment) doesn't work as well as I'd hoped. I had dreamed of being able to do this: newtype Age' n = MkAge n
-- Term-level
type Age = Age' Numeric.Natural.Natural
-- Type-level
type PAge = Age' GHC.TypeNats.Nat
$(let handleAge :: Name -> Name
handleAge n
| n == ''Age = ''PAge
| otherwise = n in
withOptions defaultOptions{promotedDataTypeOrConName = handleAge} $
genSingletons [''Age]) However, that doesn't quite do what I would expect. Because The good news is that you can make this work if you just make newtype Age = MkAge Natural
newtype PAge = PMkAge Nat
$(let customPromote :: Name -> Name
customPromote n
| n == ''Age = ''PAge
| n == 'MkAge = 'PMkAge
| n == ''Natural = ''Nat
| otherwise = promotedDataTypeOrConName defaultOptions n
customDefun :: Name -> Int -> Name
customDefun n sat = defunctionalizedName defaultOptions (customPromote n) sat in
withOptions defaultOptions{ promotedDataTypeOrConName = customPromote
, defunctionalizedName = customDefun
} $ do
decs1 <- genSingletons [''Age]
decs2 <- singletons $ lift [d|
f :: Age -> Age
f (MkAge (n :: Natural)) = MkAge (n :: Natural)
|]
return $ decs1 ++ decs2) There are a couple of interesting remarks to be made here:
I think I'm happy with this approach. Do others have any opinions on this design? |
This allows teaching the Template Haskell machinery about data types that need to be promoted in special ways (e.g., promoting `Nat` to `Natural`). This makes working with such types much less painful than before, where one always had to resort to writing such code by hand. Fixes #450.
I heard no objections, so I'll go ahead and move forward with this. See #462. |
This allows teaching the Template Haskell machinery about data types that need to be promoted in special ways (e.g., promoting `Nat` to `Natural`). This makes working with such types much less painful than before, where one always had to resort to writing such code by hand. Fixes #450.
This allows teaching the Template Haskell machinery about data types that need to be promoted in special ways (e.g., promoting `Nat` to `Natural`). This makes working with such types much less painful than before, where one always had to resort to writing such code by hand. Fixes #450.
The current state of the art for promoting data types that contain arrow or literal types is rather shoddy. First, you have to declare a base type, like so:
Next, you have to explicitly create term- and type-level versions of this base type:
As shown in the
Sing
andSingKind
instances above, if you want to actually use these types, it is almost guaranteed that you won't be able to usesingletons
' TH machinery to do so. This is becausesingletons
is not aware of the relationship betweenAge
andPAge
, so it cannot know to promoteAge
toPAge
. As a result, you basically have no choice but to promote all of yourAge
-related code by hand. Bummer.But things don't have to be this terrible. Now that
singletons
has the ability to hook into how it generates names during promotion, there is a viable way to teachsingletons
to promoteAge
toPAge
. In particular, I propose doing the following:D.S.TH.Options.Options
calledpromotedDataTypeName :: Name -> Name
(in the spirit of the existingsingledDataTypeName
). By default,promotedDataTypeName = id
sinceDataKinds
automatically promotes data type names. The interesting part comes next:Age'
,Age
, andPAge
as above. However, we no longer need to write out theSing
,SingKind
, etc. instances by hand. Instead, we can let the TH machinery do it by hooking intopromotedDataTypeName
like so:Now,
singletons
will have the foresight to promoteAge
toPAge
automatically. This should make usingAge
/PAge
much less painful in practice.The text was updated successfully, but these errors were encountered: