packages feed

constraints-extras 0.3.0.2 → 0.3.1.0

raw patch · 4 files changed

+194/−77 lines, 4 filesdep ~basedep ~constraintsdep ~template-haskellPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependency ranges changed: base, constraints, template-haskell

API changes (from Hackage documentation)

- Data.Constraint.Compose: instance forall k1 k2 (p :: k2 -> GHC.Types.Constraint) (f :: k1 -> k2) (a :: k1). p (f a) => Data.Constraint.Compose.ComposeC p f a
+ Data.Constraint.Compose: instance forall k2 k1 (p :: k2 -> GHC.Types.Constraint) (f :: k1 -> k2) (a :: k1). p (f a) => Data.Constraint.Compose.ComposeC p f a
+ Data.Constraint.Extras: --
+ Data.Constraint.Extras: -- </pre>
+ Data.Constraint.Extras: -- <pre>
+ Data.Constraint.Extras: -- ConstraintsFor Show Tag = (Show Int, Show Bool)
+ Data.Constraint.Extras: -- a <tt>f a</tt>.
+ Data.Constraint.Extras: -- | Apply <tt>c</tt> to each possible type <tt>a</tt> that could appear in
+ Data.Constraint.Flip: class c h g => FlipC (c :: k -> k' -> Constraint) (g :: k') (h :: k)
+ Data.Constraint.Flip: instance forall k k' (c :: k -> k' -> GHC.Types.Constraint) (h :: k) (g :: k'). c h g => Data.Constraint.Flip.FlipC c g h
- Data.Constraint.Extras: class ArgDict (c :: k -> Constraint) (f :: k -> *) where {
+ Data.Constraint.Extras: class ArgDict (c :: k -> Constraint) (f :: k -> Type) where {
- Data.Constraint.Extras: type ConstraintsForV (f :: (k -> k') -> *) (c :: k' -> Constraint) (g :: k) = ConstraintsFor f (FlipC (ComposeC c) g)
+ Data.Constraint.Extras: type ConstraintsForV (f :: (k -> k') -> Type) (c :: k' -> Constraint) (g :: k) = ConstraintsFor f (FlipC (ComposeC c) g)
- Data.Constraint.Extras: type family ConstraintsFor f (c :: k -> Constraint) :: Constraint;
+ Data.Constraint.Extras: type family ConstraintsFor f c :: Constraint;
- Data.Constraint.Extras.TH: gadtIndices :: Name -> Name -> Q [Either Type Type]
+ Data.Constraint.Extras.TH: gadtIndices :: Name -> [Con] -> Q [Either Type Type]

Files

ChangeLog.md view
@@ -1,5 +1,14 @@ # Revision history for constraints-extras +## 0.3.1.0++* Allow deriving instances with `deriveArgDict` for data and newtype family instances by supplying the name of one of its constructors+* Support GHC 9.0.1++## 0.3.0.3 - 2020-06-22++* Update version bounds for GHC 8.10+ ## 0.3.0.2 - 2019-09-30  * Update version bounds for GHC 8.8
constraints-extras.cabal view
@@ -1,5 +1,5 @@ name: constraints-extras-version: 0.3.0.2+version: 0.3.1.0 synopsis: Utility package for constraints description: Convenience functions and TH for working with constraints. See <https://github.com/obsidiansystems/constraints-extras/blob/develop/README.md README.md> for example usage. category: Constraints@@ -13,7 +13,7 @@ build-type: Simple cabal-version: 2.0 tested-with:-  GHC  ==8.0.2 || ==8.2.2 || ==8.4.4 || ==8.6.5 || ==8.8.1+  GHC  ==8.0.2 || ==8.2.2 || ==8.4.4 || ==8.6.5 || ==8.8.1 || ==8.10.1 || ==9.0.1 extra-source-files: README.md                     ChangeLog.md @@ -24,7 +24,7 @@   exposed-modules: Data.Constraint.Extras                  , Data.Constraint.Extras.TH                  , Data.Constraint.Compose-  other-modules: Data.Constraint.Flip+                 , Data.Constraint.Flip   other-extensions: LambdaCase                   , MultiParamTypeClasses                   , QuasiQuotes@@ -32,18 +32,18 @@                   , TypeOperators                   , ConstraintKinds                   , TemplateHaskell-  build-depends: base >=4.9 && <4.14-               , constraints >= 0.9 && < 0.12-               , template-haskell >=2.11 && <2.16+  build-depends: base >=4.9 && <4.16+               , constraints >= 0.9 && < 0.13+               , template-haskell >=2.11 && <2.18   hs-source-dirs:  src   default-language: Haskell2010  executable readme   if !flag(build-readme)     buildable: False-  build-depends: base >=4.9 && <4.14+  build-depends: base                , aeson-               , constraints >= 0.9 && < 0.12+               , constraints                , constraints-extras   main-is: README.lhs   ghc-options: -Wall -optL -q
src/Data/Constraint/Extras.hs view
@@ -11,34 +11,86 @@ {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE UndecidableSuperClasses #-} -module Data.Constraint.Extras where+-- | Throughout this module, we use the following GADT and @ArgDict@ instance+-- in our examples:+--+-- > {-# LANGUAGE StandaloneDeriving #-}+-- >+-- > data Tag a where+-- >   I :: Tag Int+-- >   B :: Tag Bool+-- > deriving instance Show (Tag a)+-- >+-- > $(deriveArgDict ''Tag)+--+-- The constructors of @Tag@ mean that a type variable @a@ in @Tag a@+-- must come from the set { @Int@, @Bool@ }. We call this the "set of+-- types @a@ that could be applied to @Tag@".+module Data.Constraint.Extras+  ( -- * The ArgDict typeclass+    ArgDict(..)+  , ConstraintsFor'+  , argDict'+  , ConstraintsForV+  , argDictV+    -- * Bringing instances into scope+  , Has+  , has+  , Has'+  , has'+  , HasV+  , hasV+  , whichever+    -- * Misc+  , Implies1(..)+    -- * Deprecated+  , ArgDictV+  ) where  import Data.Constraint import Data.Constraint.Compose import Data.Constraint.Flip import Data.Constraint.Forall+import Data.Kind --- | Morally, this class is for GADTs whose indices can be finitely enumerated. It provides operations which will--- select the appropriate type class dictionary from among a list of contenders based on a value of the type.--- There are a few different variations of this which we'd like to be able to support, and they're all implemented--- in the same fashion at the term level, by pattern matching on the constructors of the GADT, and producing Dict--- as the result.--- It would be nice to have some way to stop the proliferation of these variants and unify the existing ones, but--- at the moment, it appears to require honest type level functions. (Closed type families which must be fully--- applied didn't quite cut it when I tried). Some symbolic type-level application could do the trick, but I didn't--- want to go quite that far at the time of writing.+-- | Morally, this class is for GADTs whose indices can be finitely+-- enumerated. An @'ArgDict' c f@ instance allows us to do two things:+--+-- 1. 'ConstraintsFor' requests the set of constraints @c a@ for all+--    possible types @a@ that could be applied to @f@.+--+-- 2. 'argDict' selects a specific @c a@ given a value of type @f a@.+--+-- Use 'Data.Constraint.Extras.TH.deriveArgDict' to derive instances+-- of this class.+class ArgDict (c :: k -> Constraint) (f :: k -> Type) where+  -- | Apply @c@ to each possible type @a@ that could appear in a @f a@.+  --+  -- > ConstraintsFor Show Tag = (Show Int, Show Bool)+  type ConstraintsFor f c :: Constraint -class ArgDict (c :: k -> Constraint) (f :: k -> *) where-  type ConstraintsFor f (c :: k -> Constraint) :: Constraint+  -- | Use an @f a@ to select a specific dictionary from @ConstraintsFor f c@.+  --+  -- > argDict I :: Dict (Show Int)   argDict :: ConstraintsFor f c => f a -> Dict (c a) +-- | \"Primed\" variants (@ConstraintsFor'@, 'argDict'', 'Has'',+-- 'has'', &c.) use the 'ArgDict' instance on @f@ to apply constraints+-- on @g a@ instead of just @a@. This is often useful when you have+-- data structures parameterised by something of kind @(x -> Type) ->+-- Type@, like in the @dependent-sum@ and @dependent-map@ libraries.+--+-- > ConstraintsFor' Tag Show Identity = (Show (Identity Int), Show (Identity Bool)) type ConstraintsFor' f (c :: k -> Constraint) (g :: k' -> k) = ConstraintsFor f (ComposeC c g) +-- | Get a dictionary for a specific @g a@, using a value of type @f a@.+--+-- > argDict' B :: Dict (Show (Identity Bool)) argDict' :: forall f c g a. (Has' c f g) => f a -> Dict (c (g a)) argDict' tag = case argDict tag of   (Dict :: Dict (ComposeC c g a)) -> Dict -type ConstraintsForV (f :: (k -> k') -> *) (c :: k' -> Constraint) (g :: k) = ConstraintsFor f (FlipC (ComposeC c) g)+type ConstraintsForV (f :: (k -> k') -> Type) (c :: k' -> Constraint) (g :: k) = ConstraintsFor f (FlipC (ComposeC c) g)  argDictV :: forall f c g v. (HasV c f g) => f v -> Dict (c (v g)) argDictV tag = case argDict tag of@@ -47,23 +99,59 @@ {-# DEPRECATED ArgDictV "Just use 'ArgDict'" #-} type ArgDictV f c = ArgDict f c +-- | @Has c f@ is a constraint which means that for every type @a@+-- that could be applied to @f@, we have @c a@.+--+-- > Has Show Tag = (ArgDict Show Tag, Show Int, Show Bool) type Has (c :: k -> Constraint) f = (ArgDict c f, ConstraintsFor f c)++-- | @Has' c f g@ is a constraint which means that for every type @a@+-- that could be applied to @f@, we have @c (g a)@.+--+-- > Has' Show Tag Identity = (ArgDict (Show . Identity) Tag, Show (Identity Int), Show (Identity Bool)) type Has' (c :: k -> Constraint) f (g :: k' -> k) = (ArgDict (ComposeC c g) f, ConstraintsFor' f c g) type HasV c f g = (ArgDict (FlipC (ComposeC c) g) f, ConstraintsForV f c g) -has :: forall c f a r. (Has c f) => f a -> (c a => r) -> r+-- | Use the @a@ from @f a@ to select a specific @c a@ constraint, and+-- bring it into scope. The order of type variables is chosen to work+-- with @-XTypeApplications@.+--+-- > -- Hold an a, along with a tag identifying the a.+-- > data SomeTagged tag where+-- >   SomeTagged :: a -> tag a -> SomeTagged tag+-- >+-- > -- Use the stored tag to identify the thing we have, allowing us to call 'show'. Note that we+-- > -- have no knowledge of the tag type.+-- > showSomeTagged :: Has Show tag => SomeTagged tag -> String+-- > showSomeTagged (SomeTagged a tag) = has @Show tag $ show a+has :: forall c f a r. Has c f => f a -> (c a => r) -> r has k r | (Dict :: Dict (c a)) <- argDict k = r -has' :: forall c g f a r. (Has' c f g) => f a -> (c (g a) => r) -> r+-- | Like 'has', but we get a @c (g a)@ instance brought into scope+-- instead. Use @-XTypeApplications@ to specify @c@ and @g@.+--+-- > -- From dependent-sum:Data.Dependent.Sum+-- > data DSum tag f = forall a. !(tag a) :=> f a+-- >+-- > -- Show the value from a dependent sum. (We'll need 'whichever', discussed later, to show the key.)+-- > showDSumVal :: forall tag f . Has' Show tag f => DSum tag f -> String+-- > showDSumVal (tag :=> fa) = has' @Show @f tag $ show fa+has' :: forall c g f a r. Has' c f g => f a -> (c (g a) => r) -> r has' k r | (Dict :: Dict (c (g a))) <- argDict' k = r -hasV :: forall c g f v r. (HasV c f g) => f v -> (c (v g) => r) -> r+hasV :: forall c g f v r. HasV c f g => f v -> (c (v g) => r) -> r hasV k r | (Dict :: Dict (c (v g))) <- argDictV k = r -whichever :: forall c t a r. (ForallF c t) => (c (t a) => r) -> r+-- | Given "forall a. @c (t a)@" (the @ForallF c t@ constraint), select a+-- specific @a@, and bring @c (t a)@ into scope. Use @-XTypeApplications@ to+-- specify @c@, @t@ and @a@.+--+-- > -- Show the tag of a dependent sum, even though we don't know the tag type.+-- > showDSumKey :: forall tag f . ForallF Show tag => DSum tag f -> String+-- > showDSumKey ((tag :: tag a) :=> fa) = whichever @Show @tag @a $ show tag+whichever :: forall c t a r. ForallF c t => (c (t a) => r) -> r whichever r = r \\ (instF :: ForallF c t :- c (t a)) --- | Allows explicit specification of constraint implication+-- | Allows explicit specification of constraint implication. class Implies1 c d where   implies1 :: c a :- d a-
src/Data/Constraint/Extras/TH.hs view
@@ -12,54 +12,50 @@  deriveArgDict :: Name -> Q [Dec] deriveArgDict n = do+  (typeHead, constrs) <- getDeclInfo n   c <- newName "c"-  ts <- gadtIndices c n+  ts <- gadtIndices c constrs   let xs = flip map ts $ \case         Left t -> AppT (AppT (ConT ''ConstraintsFor) t) (VarT c)         Right t -> (AppT (VarT c) t)       l = length xs       constraints = foldl AppT (TupleT l) xs-  arity <- tyConArity n-  tyVars <- replicateM (arity - 1) (newName "a")-  let n' = foldr (\v x -> AppT x (VarT v)) (ConT n) tyVars-  [d| instance ArgDict $(varT c) $(pure n') where-        type ConstraintsFor  $(pure n') $(varT c) = $(pure constraints)-        argDict = $(LamCaseE <$> matches c n 'argDict)+  [d| instance ArgDict $(varT c) $(pure typeHead) where+        type ConstraintsFor  $(pure typeHead) $(varT c) = $(pure constraints)+        argDict = $(LamCaseE <$> matches c constrs 'argDict)     |]  {-# DEPRECATED deriveArgDictV "Just use 'deriveArgDict'" #-} deriveArgDictV :: Name -> Q [Dec] deriveArgDictV = deriveArgDict -matches :: Name -> Name -> Name -> Q [Match]-matches c n argDictName = do+matches :: Name -> [Con] -> Name -> Q [Match]+matches c constrs argDictName = do   x <- newName "x"-  reify n >>= \case-    TyConI (DataD _ _ _ _ constrs _) -> fmap concat $ forM constrs $ \case-      GadtC [name] _ _ -> return $-        [Match (RecP name []) (NormalB $ ConE 'Dict) []]-      ForallC _ _ (GadtC [name] bts (AppT _ (VarT b))) -> do-        ps <- forM bts $ \case-          (_, AppT t (VarT b')) | b == b' -> do-            hasArgDictInstance <- not . null <$> reifyInstances ''ArgDict [VarT c, t]-            return $ if hasArgDictInstance-              then Just x-              else Nothing-          _ -> return Nothing-        return $ case catMaybes ps of-          [] -> [Match (RecP name []) (NormalB $ ConE 'Dict) []]-          (v:_) ->-            let patf = \v' rest done -> if done-                  then WildP : rest done-                  else case v' of-                    Nothing -> WildP : rest done-                    Just _ -> VarP v : rest True-                pat = foldr patf (const []) ps False-            in [Match (ConP name pat) (NormalB $ AppE (VarE argDictName) (VarE v)) []]-      ForallC _ _ (GadtC [name] _ _) -> return $-        [Match (RecP name []) (NormalB $ ConE 'Dict) []]-      a -> error $ "deriveArgDict matches: Unmatched 'Dec': " ++ show a-    a -> error $ "deriveArgDict matches: Unmatched 'Info': " ++ show a+  fmap concat $ forM constrs $ \case+    GadtC [name] _ _ -> return $+      [Match (RecP name []) (NormalB $ ConE 'Dict) []]+    ForallC _ _ (GadtC [name] bts (AppT _ (VarT b))) -> do+      ps <- forM bts $ \case+        (_, AppT t (VarT b')) | b == b' -> do+          hasArgDictInstance <- not . null <$> reifyInstances ''ArgDict [VarT c, t]+          return $ if hasArgDictInstance+            then Just x+            else Nothing+        _ -> return Nothing+      return $ case catMaybes ps of+        [] -> [Match (RecP name []) (NormalB $ ConE 'Dict) []]+        (v:_) ->+          let patf = \v' rest done -> if done+                then WildP : rest done+                else case v' of+                  Nothing -> WildP : rest done+                  Just _ -> VarP v : rest True+              pat = foldr patf (const []) ps False+          in [Match (ConP name pat) (NormalB $ AppE (VarE argDictName) (VarE v)) []]+    ForallC _ _ (GadtC [name] _ _) -> return $+      [Match (RecP name []) (NormalB $ ConE 'Dict) []]+    a -> error $ "deriveArgDict matches: Unmatched 'Dec': " ++ show a  kindArity :: Kind -> Int kindArity = \case@@ -69,20 +65,44 @@   ParensT t -> kindArity t   _ -> 0 -tyConArity :: Name -> Q Int-tyConArity n = reify n >>= return . \case-   TyConI (DataD _ _ ts mk _ _) -> fromMaybe 0 (fmap kindArity mk) + length ts-   _ -> error $ "tyConArity: Supplied name reified to something other than a data declaration: " ++ show n+getDeclInfo :: Name -> Q (Type, [Con])+getDeclInfo n = reify n >>= \case+  TyConI (DataD _ _ ts mk constrs _) -> do+    let arity = fromMaybe 0 (fmap kindArity mk) + length ts+    tyVars <- replicateM (arity - 1) (newName "a")+    let typeHead = foldr (\v x -> AppT x (VarT v)) (ConT n) tyVars+    return (typeHead, constrs)+  DataConI _ (ForallT _ _ (AppT typeHead _)) parent -> do+    reify parent >>= \case+      FamilyI _ instances -> do+        let instCons :: InstanceDec -> [Con]+            instCons = \case+              DataInstD _ _ _ _ cons _ -> cons+              NewtypeInstD _ _ _ _ con _ -> [con]+              _ -> error $ "getDeclInfo: Expected a data or newtype family instance"+            conNames :: Con -> [Name]+            conNames = \case+              NormalC other _ -> [other]+              RecC other _ -> [other]+              InfixC _ other _ -> [other]+              ForallC _ _ con -> conNames con+              GadtC others _ _ -> others+              RecGadtC others _ _ -> others+            instHasThisConstructor i = any (== n) $ conNames =<< instCons i+        case filter instHasThisConstructor instances of+          [] -> error $ "getDeclInfo: Couldn't find data family instance for constructor " ++ show n+          l@(_:_:_) -> error $ "getDeclInfo: Expected one data family instance for constructor " ++ show n ++ " but found multiple: " ++ show l+          [i] -> return (typeHead, instCons i)+      a -> error $ "getDeclInfo: Unmatched parent of data family instance: " ++ show a+  a -> error $ "getDeclInfo: Unmatched 'Info': " ++ show a -gadtIndices :: Name -> Name -> Q [Either Type Type]-gadtIndices c n = reify n >>= \case-  TyConI (DataD _ _ _ _ constrs _) -> fmap concat $ forM constrs $ \case-    GadtC _ _ (AppT _ typ) -> return [Right typ]-    ForallC _ _ (GadtC _ bts (AppT _ (VarT _))) -> fmap concat $ forM bts $ \case-      (_, AppT t (VarT _)) -> do-        hasArgDictInstance <- fmap (not . null) $ reifyInstances ''ArgDict [VarT c, t]-        return $ if hasArgDictInstance then [Left t] else []-      _ -> return []-    ForallC _ _ (GadtC _ _ (AppT _ typ)) -> return [Right typ]+gadtIndices :: Name -> [Con] -> Q [Either Type Type]+gadtIndices c constrs = fmap concat $ forM constrs $ \case+  GadtC _ _ (AppT _ typ) -> return [Right typ]+  ForallC _ _ (GadtC _ bts (AppT _ (VarT _))) -> fmap concat $ forM bts $ \case+    (_, AppT t (VarT _)) -> do+      hasArgDictInstance <- fmap (not . null) $ reifyInstances ''ArgDict [VarT c, t]+      return $ if hasArgDictInstance then [Left t] else []     _ -> return []-  a -> error $ "gadtIndices: Unmatched 'Info': " ++ show a+  ForallC _ _ (GadtC _ _ (AppT _ typ)) -> return [Right typ]+  _ -> return []