Skip to content

Commit

Permalink
Make singletons deterministic
Browse files Browse the repository at this point in the history
There were several places in `singletons` that would compute entirely
different things depending on the order in which GHC's unique numbers
happened to be computed. Like the corresponding `th-desugar` patch
(in goldfirere/th-desugar#115), this fixes these issues by swapping
out nondeterministic uses of `Map` and `Set` with `th-desugar`'s new
`OMap` and `OSet` data structures, which remember the order in which
elements were inserted.

This patch looks large, but half of the modifications are routine
changes brought about by switching data structures, and the other
half are test suite wibbles brought about by `singletons` settling
on a deterministic order for the expected output. The upshot is that
we can finally run the `singletons` test suite with
`-dunique-increment=-1` and have it still pass! Hooray!

Fixes #367.
  • Loading branch information
RyanGlScott committed Mar 5, 2019
1 parent ea63077 commit 0a175d9
Show file tree
Hide file tree
Showing 40 changed files with 1,185 additions and 1,124 deletions.
4 changes: 2 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ install:
- "echo 'source-repository-package' >> cabal.project"
- "echo ' type: git' >> cabal.project"
- "echo ' location: https://github.com/goldfirere/th-desugar' >> cabal.project"
- "echo ' tag: 491028b6b33abe5e65a1b52cc019f07af6e890ef' >> cabal.project"
- "echo ' tag: c67e84d2f6fdfd2aab1af3a86f646a7cc805d668' >> cabal.project"
- "echo 'package th-desugar' >> cabal.project"
- "echo ' tests: False' >> cabal.project"
- "echo ' benchmarks: False' >> cabal.project"
Expand Down Expand Up @@ -115,7 +115,7 @@ script:
- "echo 'source-repository-package' >> cabal.project"
- "echo ' type: git' >> cabal.project"
- "echo ' location: https://github.com/goldfirere/th-desugar' >> cabal.project"
- "echo ' tag: 491028b6b33abe5e65a1b52cc019f07af6e890ef' >> cabal.project"
- "echo ' tag: c67e84d2f6fdfd2aab1af3a86f646a7cc805d668' >> cabal.project"
- "echo 'package th-desugar' >> cabal.project"
- "echo ' tests: False' >> cabal.project"
- "echo ' benchmarks: False' >> cabal.project"
Expand Down
2 changes: 1 addition & 1 deletion cabal.project
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ packages: .
source-repository-package
type: git
location: https://github.com/goldfirere/th-desugar
tag: 491028b6b33abe5e65a1b52cc019f07af6e890ef
tag: c67e84d2f6fdfd2aab1af3a86f646a7cc805d668
6 changes: 3 additions & 3 deletions src/Data/Singletons/Deriving/Util.hs
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ module Data.Singletons.Deriving.Util where

import Control.Monad
import Data.List
import qualified Data.Set as Set
import Data.Singletons.Names
import Data.Singletons.Syntax
import Data.Singletons.Util
import Language.Haskell.TH.Desugar
import qualified Language.Haskell.TH.Desugar.OSet as OSet
import Language.Haskell.TH.Syntax

-- A generic type signature for describing how to produce a derived instance.
Expand Down Expand Up @@ -213,7 +213,7 @@ functorLikeValidityChecks allowConstrainedLastTyVar (DataDecl n data_tvbs cons)
= do ex_tvbs <- conExistentialTvbs (foldTypeTvbs (DConT n) data_tvbs) con
let univ_tvb_names = map extractTvbName con_tvbs \\ map extractTvbName ex_tvbs
if last_tv `elem` univ_tvb_names
&& last_tv `Set.notMember` foldMap fvDType con_theta
&& last_tv `OSet.notMember` foldMap fvDType con_theta
then pure ()
else fail $ badCon con_name existential
| otherwise
Expand Down Expand Up @@ -248,7 +248,7 @@ deepSubtypesContaining tv
, ft_forall = \tvbs xs -> filter (\x -> all (not_in_ty x) tvbs) xs })
where
not_in_ty :: DType -> DTyVarBndr -> Bool
not_in_ty ty tvb = extractTvbName tvb `Set.notMember` fvDType ty
not_in_ty ty tvb = extractTvbName tvb `OSet.notMember` fvDType ty

-- Fold over the arguments of a data constructor in a Functor-like way.
foldDataConArgs :: forall q a. DsMonad q => FFoldType a -> DCon -> q [a]
Expand Down
6 changes: 4 additions & 2 deletions src/Data/Singletons/Partition.hs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ import Data.Singletons.Names
import Language.Haskell.TH.Syntax hiding (showName)
import Language.Haskell.TH.Ppr
import Language.Haskell.TH.Desugar
import qualified Language.Haskell.TH.Desugar.OMap.Strict as OMap
import Language.Haskell.TH.Desugar.OMap.Strict (OMap)
import Data.Singletons.Util

import Control.Monad
Expand Down Expand Up @@ -174,14 +176,14 @@ partitionClassDec _ =

partitionInstanceDec :: Monad m => DDec
-> m ( Maybe (Name, ULetDecRHS) -- right-hand sides of methods
, Map Name DType -- method type signatures
, OMap Name DType -- method type signatures
)
partitionInstanceDec (DLetDec (DValD (DVarP name) exp)) =
pure (Just (name, UValue exp), mempty)
partitionInstanceDec (DLetDec (DFunD name clauses)) =
pure (Just (name, UFunction clauses), mempty)
partitionInstanceDec (DLetDec (DSigD name ty)) =
pure (Nothing, Map.singleton name ty)
pure (Nothing, OMap.singleton name ty)
partitionInstanceDec (DLetDec (DPragmaD {})) =
pure (Nothing, mempty)
partitionInstanceDec (DTySynInstD {}) =
Expand Down
66 changes: 34 additions & 32 deletions src/Data/Singletons/Promote.hs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ module Data.Singletons.Promote where
import Language.Haskell.TH hiding ( Q, cxt )
import Language.Haskell.TH.Syntax ( Quasi(..) )
import Language.Haskell.TH.Desugar
import qualified Language.Haskell.TH.Desugar.OMap.Strict as OMap
import Language.Haskell.TH.Desugar.OMap.Strict (OMap)
import qualified Language.Haskell.TH.Desugar.OSet as OSet
import Language.Haskell.TH.Desugar.OSet (OSet)
import Data.Singletons.Names
import Data.Singletons.Promote.Monad
import Data.Singletons.Promote.Eq
Expand All @@ -35,8 +39,6 @@ import Control.Monad.Trans.Maybe
import Control.Monad.Writer
import qualified Data.Map.Strict as Map
import Data.Map.Strict ( Map )
import qualified Data.Set as Set
import Data.Set ( Set )
import Data.Maybe
import qualified GHC.LanguageExtensions.Type as LangExt

Expand Down Expand Up @@ -162,7 +164,7 @@ promoteInstance mk_inst class_name name = do
cons' <- concatMapM (dsCon tvbs' data_ty) cons
let data_decl = DataDecl name tvbs' cons'
raw_inst <- mk_inst Nothing data_ty data_decl
decs <- promoteM_ [] $ void $ promoteInstanceDec Map.empty raw_inst
decs <- promoteM_ [] $ void $ promoteInstanceDec OMap.empty raw_inst
return $ decsToTH decs

promoteInfo :: DInfo -> PrM ()
Expand Down Expand Up @@ -255,12 +257,12 @@ promoteLetDecs prefixes decls = do
let_dec_env <- buildLetDecEnv decls
all_locals <- allLocals
let binds = [ (name, foldType (DConT sym) (map DVarT all_locals))
| name <- Map.keys $ lde_defns let_dec_env
| (name, _) <- OMap.assocs $ lde_defns let_dec_env
, let proName = promoteValNameLhsPrefix prefixes name
sym = promoteTySym proName (length all_locals) ]
(decs, let_dec_env') <- letBind binds $ promoteLetDecEnv prefixes let_dec_env
emitDecs decs
return (binds, let_dec_env' { lde_proms = Map.fromList binds })
return (binds, let_dec_env' { lde_proms = OMap.fromList binds })

-- Promotion of data types to kinds is automatic (see "Giving Haskell a
-- Promotion" paper for more details). Here we "plug into" the promotion
Expand Down Expand Up @@ -310,29 +312,29 @@ promoteClassDec decl@(ClassDecl { cd_cxt = cxt
let pClsName = promoteClassName cls_name
pCxt <- mapM promote_superclass_pred cxt
forallBind cls_kvs_to_bind $ do
sig_decs <- mapM (uncurry promote_sig) (Map.toList meth_sigs)
let defaults_list = Map.toList defaults
sig_decs <- mapM (uncurry promote_sig) (OMap.assocs meth_sigs)
let defaults_list = OMap.assocs defaults
defaults_names = map fst defaults_list
(default_decs, ann_rhss, prom_rhss)
<- mapAndUnzip3M (promoteMethod Map.empty Nothing meth_sigs) defaults_list
<- mapAndUnzip3M (promoteMethod OMap.empty Nothing meth_sigs) defaults_list

let infix_decls' = catMaybes $ map (uncurry promoteInfixDecl)
$ Map.toList infix_decls
$ OMap.assocs infix_decls

-- no need to do anything to the fundeps. They work as is!
emitDecs [DClassD pCxt pClsName tvbs fundeps
(sig_decs ++ default_decs ++ infix_decls')]
let defaults_list' = zip defaults_names ann_rhss
proms = zip defaults_names prom_rhss
cls_kvs_to_bind' = cls_kvs_to_bind <$ meth_sigs
return (decl { cd_lde = lde { lde_defns = Map.fromList defaults_list'
, lde_proms = Map.fromList proms
return (decl { cd_lde = lde { lde_defns = OMap.fromList defaults_list'
, lde_proms = OMap.fromList proms
, lde_bound_kvs = cls_kvs_to_bind' } })
where
cls_kvb_names, cls_tvb_names, cls_kvs_to_bind :: Set Name
cls_kvb_names, cls_tvb_names, cls_kvs_to_bind :: OSet Name
cls_kvb_names = foldMap (foldMap fvDType . extractTvbKind) tvbs'
cls_tvb_names = Set.fromList $ map extractTvbName tvbs'
cls_kvs_to_bind = cls_kvb_names `Set.union` cls_tvb_names
cls_tvb_names = OSet.fromList $ map extractTvbName tvbs'
cls_kvs_to_bind = cls_kvb_names OSet.|<> cls_tvb_names

promote_sig :: Name -> DType -> PrM DDec
promote_sig name ty = do
Expand Down Expand Up @@ -362,7 +364,7 @@ promoteClassDec decl@(ClassDecl { cd_cxt = cxt
go DArrowT = fail "(->) spotted at head of a constraint"

-- returns (unpromoted method name, ALetDecRHS) pairs
promoteInstanceDec :: Map Name DType -> UInstDecl -> PrM AInstDecl
promoteInstanceDec :: OMap Name DType -> UInstDecl -> PrM AInstDecl
promoteInstanceDec orig_meth_sigs
decl@(InstDecl { id_name = cls_name
, id_arg_tys = inst_tys
Expand Down Expand Up @@ -432,11 +434,11 @@ dsReifyTypeNameInfo, which first calls lookupTypeName (to ensure we can find a N
that's in the type namespace) and _then_ reifies it.
-}

promoteMethod :: Map Name DType -- InstanceSigs for methods
promoteMethod :: OMap Name DType -- InstanceSigs for methods
-> Maybe (Map Name DKind)
-- ^ instantiations for class tyvars (Nothing for default decls)
-- See Note [Promoted class method kinds]
-> Map Name DType -- method types
-> OMap Name DType -- method types
-> (Name, ULetDecRHS)
-> PrM (DDec, ALetDecRHS, DType)
-- returns (type instance, ALetDecRHS, promoted RHS)
Expand All @@ -462,7 +464,7 @@ promoteMethod inst_sigs_map m_subst orig_sigs_map (meth_name, meth_rhs) = do
family_args = map DVarT meth_arg_tvs
helperName <- newUniqueName helperNameBase
((_, _, _, eqns), _defuns, ann_rhs)
<- promoteLetDecRHS (Just (meth_arg_kis, meth_res_ki)) Map.empty Map.empty
<- promoteLetDecRHS (Just (meth_arg_kis, meth_res_ki)) OMap.empty OMap.empty
noPrefix helperName meth_rhs
let tvbs = zipWith DKindedTV meth_arg_tvs meth_arg_kis
emitDecs [DClosedTypeFamilyD (DTypeFamilyHead
Expand All @@ -483,7 +485,7 @@ promoteMethod inst_sigs_map m_subst orig_sigs_map (meth_name, meth_rhs) = do

lookup_meth_ty :: PrM ([DKind], DKind)
lookup_meth_ty =
case Map.lookup meth_name inst_sigs_map of
case OMap.lookup meth_name inst_sigs_map of
Just ty ->
-- We have an InstanceSig. These are easy: no substitution for clas
-- variables is required at all!
Expand All @@ -492,7 +494,7 @@ promoteMethod inst_sigs_map m_subst orig_sigs_map (meth_name, meth_rhs) = do
-- We don't have an InstanceSig, so we must compute the kind to use
-- ourselves (possibly substituting for class variables below).
(arg_kis, res_ki) <-
case Map.lookup meth_name orig_sigs_map of
case OMap.lookup meth_name orig_sigs_map of
Nothing -> do
mb_info <- dsReifyTypeNameInfo proName
-- See Note [Using dsReifyTypeNameInfo when promoting instances]
Expand Down Expand Up @@ -553,10 +555,10 @@ promoteLetDecEnv prefixes (LetDecEnv { lde_defns = value_env
, lde_types = type_env
, lde_infix = fix_env }) = do
let infix_decls = catMaybes $ map (uncurry promoteInfixDecl)
$ Map.toList fix_env
$ OMap.assocs fix_env

-- promote all the declarations, producing annotated declarations
let (names, rhss) = unzip $ Map.toList value_env
let (names, rhss) = unzip $ OMap.assocs value_env
(payloads, defun_decss, ann_rhss)
<- fmap unzip3 $ zipWithM (promoteLetDecRHS Nothing type_env fix_env prefixes) names rhss

Expand All @@ -565,11 +567,11 @@ promoteLetDecEnv prefixes (LetDecEnv { lde_defns = value_env
let decs = map payload_to_dec payloads ++ infix_decls

-- build the ALetDecEnv
let let_dec_env' = LetDecEnv { lde_defns = Map.fromList $ zip names ann_rhss
let let_dec_env' = LetDecEnv { lde_defns = OMap.fromList $ zip names ann_rhss
, lde_types = type_env
, lde_infix = fix_env
, lde_proms = Map.empty -- filled in promoteLetDecs
, lde_bound_kvs = Map.fromList $ map (, bound_kvs) names }
, lde_proms = OMap.empty -- filled in promoteLetDecs
, lde_bound_kvs = OMap.fromList $ map (, bound_kvs) names }

return (decs, let_dec_env')
where
Expand Down Expand Up @@ -601,8 +603,8 @@ promoteInfixDecl name fixity
-- an intermediate structure. Perhaps a better design is available.
promoteLetDecRHS :: Maybe ([DKind], DKind) -- the promoted type of the RHS (if known)
-- needed to fix #136
-> Map Name DType -- local type env't
-> Map Name Fixity -- local fixity env't
-> OMap Name DType -- local type env't
-> OMap Name Fixity -- local fixity env't
-> (String, String) -- let-binding prefixes
-> Name -- name of the thing being promoted
-> ULetDecRHS -- body of the thing
Expand All @@ -614,7 +616,7 @@ promoteLetDecRHS m_rhs_ki type_env fix_env prefixes name (UValue exp) = do
<- case m_rhs_ki of
Just (arg_kis, res_ki) -> return ( Just (ravelTyFun (arg_kis ++ [res_ki]))
, length arg_kis )
_ | Just ty <- Map.lookup name type_env
_ | Just ty <- OMap.lookup name type_env
-> do ki <- promoteType ty
return (Just ki, countArgs ty)
| otherwise
Expand All @@ -625,7 +627,7 @@ promoteLetDecRHS m_rhs_ki type_env fix_env prefixes name (UValue exp) = do
let lde_kvs_to_bind = foldMap fvDType res_kind
(exp', ann_exp) <- forallBind lde_kvs_to_bind $ promoteExp exp
let proName = promoteValNameLhsPrefix prefixes name
m_fixity = Map.lookup name fix_env
m_fixity = OMap.lookup name fix_env
tvbs = map DPlainTV all_locals
defuns <- defunctionalize proName m_fixity tvbs res_kind
return ( ( proName, tvbs, res_kind
Expand All @@ -644,7 +646,7 @@ promoteLetDecRHS m_rhs_ki type_env fix_env prefixes name (UFunction clauses) = d
numArgs <- count_args clauses
(m_argKs, m_resK, ty_num_args) <- case m_rhs_ki of
Just (arg_kis, res_ki) -> return (map Just arg_kis, Just res_ki, length arg_kis)
_ | Just ty <- Map.lookup name type_env
_ | Just ty <- OMap.lookup name type_env
-> do
-- promoteType turns arrows into TyFun. So, we unravel first to
-- avoid this behavior. Note the use of ravelTyFun in resultK
Expand All @@ -656,7 +658,7 @@ promoteLetDecRHS m_rhs_ki type_env fix_env prefixes name (UFunction clauses) = d
| otherwise
-> return (replicate numArgs Nothing, Nothing, numArgs)
let proName = promoteValNameLhsPrefix prefixes name
m_fixity = Map.lookup name fix_env
m_fixity = OMap.lookup name fix_env
all_locals <- allLocals
let local_tvbs = map DPlainTV all_locals
tyvarNames <- mapM (const $ qNewName "a") m_argKs
Expand Down Expand Up @@ -726,7 +728,7 @@ promotePat (DLitP lit) = (, ADLitP lit) <$> promoteLitPat lit
promotePat (DVarP name) = do
-- term vars can be symbols... type vars can't!
tyName <- mkTyName name
tell $ PromDPatInfos [(name, tyName)] Set.empty
tell $ PromDPatInfos [(name, tyName)] OSet.empty
return (DVarT tyName, ADVarP name)
promotePat (DConP name pats) = do
(types, pats') <- mapAndUnzipM promotePat pats
Expand Down
15 changes: 8 additions & 7 deletions src/Data/Singletons/Promote/Defun.hs
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,18 @@ This file creates defunctionalization symbols for types during promotion.
module Data.Singletons.Promote.Defun where

import Language.Haskell.TH.Desugar
import qualified Language.Haskell.TH.Desugar.OSet as OSet
import Data.Singletons.Promote.Monad
import Data.Singletons.Promote.Type
import Data.Singletons.Names
import Language.Haskell.TH.Syntax
import Data.Singletons.Syntax
import Data.Singletons.Util
import Control.Monad
import Data.Foldable
import qualified Data.Map.Strict as Map
import Data.Map.Strict (Map)
import Data.Maybe
import qualified Data.Set as Set

defunInfo :: DInfo -> PrM [DDec]
defunInfo (DTyConI dec _instances) = buildDefunSyms dec
Expand Down Expand Up @@ -193,17 +194,17 @@ defunctionalize name m_fixity m_arg_tvbs' m_res_kind' = do
-- If we cannot infer the return type, don't bother
-- trying to construct an explicit return kind.
Just tyfun ->
let bound_tvs = Set.fromList (map extractTvbName arg_params) `Set.union`
let bound_tvs = OSet.fromList (map extractTvbName arg_params) OSet.|<>
foldMap (foldMap fvDType) (map extractTvbKind arg_params)
not_bound tvb = not (extractTvbName tvb `Set.member` bound_tvs)
not_bound tvb = not (extractTvbName tvb `OSet.member` bound_tvs)
tvb_to_type tvb_name = fromMaybe (DVarT tvb_name) $
Map.lookup tvb_name tvb_to_type_map
-- Implements part (2)(iii) from
-- Note [Defunctionalization and dependent quantification]
tyfun_tvbs = filter not_bound $ -- (2)(iii)(d)
toposortTyVarsOf $ -- (2)(iii)(c)
map tvb_to_type $ -- (2)(iii)(b)
Set.toList $ fvDType tyfun -- (2)(iii)(a)
tyfun_tvbs = filter not_bound $ -- (2)(iii)(d)
toposortTyVarsOf $ -- (2)(iii)(c)
map tvb_to_type $ -- (2)(iii)(b)
toList $ fvDType tyfun -- (2)(iii)(a)
in (arg_params, Just (DForallT tyfun_tvbs [] tyfun))
app_data_ty = foldTypeTvbs (DConT data_name) m_args
app_eqn = DTySynEqn Nothing
Expand Down
Loading

0 comments on commit 0a175d9

Please sign in to comment.