impl 0.1.0.0 → 0.2.0.0
raw patch · 5 files changed
+162/−38 lines, 5 filesdep +containersPVP ok
version bump matches the API change (PVP)
Dependencies added: containers
API changes (from Hackage documentation)
- Impl: (!) :: WithParam p fn fn' => fn -> Param p -> fn'
- Impl: infixl 9 !
+ Impl: ($$) :: WithParam p fn fn' => fn -> Param p -> fn'
+ Impl: Optional :: a -> Method a
+ Impl: Required :: a -> Method a
+ Impl: arg' :: Name name -> a -> (name :? a) -> a
+ Impl: data Method a
+ Impl: data Param p
+ Impl: data Symbol
+ Impl: defaults :: Param Defaults
+ Impl: methodsFor :: Name -> TypeQ
+ Impl: type DecsQ = Q [Dec]
- Impl: type (:!) (name :: Symbol) a = NamedF Identity a name
+ Impl: type (:?) (name :: Symbol) a = NamedF Maybe a name
- Impl: type family as :-> r
+ Impl: type family (as :: [x]) ++ (bs :: [x]) :: [x]
Files
- Impl.hs +87/−12
- Impl/Utils.hs +30/−11
- example/Monad/Foo.hs +4/−2
- example/Monad/Impl.hs +37/−11
- impl.cabal +4/−2
Impl.hs view
@@ -1,28 +1,103 @@-{-# OPTIONS_HADDOCK show-extensions #-}--- | This small but extensible framework facilitates defining complex defaulting rules that are not handled by @DefaultSignatures@, and reducing the overhead of giving instances to new datatypes by generating superclasses. One reason we might want this is when a superclass wants to be given a default by two different subclasses (ex: @Bifunctor@ and @Profunctor@ both could generate @Functor@ instances). See the @example@ internal library for how to implement instances of 'Impl'.-module Impl (Impl(..),NamedMethods- -- * Reexports- ,type (:->),TypeQ,(!) ,type (:!), NamedF(Arg), arg+{-# OPTIONS_HADDOCK show-extensions,not-home #-}+-- | Impl is intended to be used as an alternative to the normal default typeclass methods machinery of Haskell.+-- In contrast with @intrinsic-superclasses@, we must specify each link of the implementation heirarchy with an instance of Impl, rather than infer it from the superclass heirarchy.+-- The benefit of this more explicit style is complete control over default methods provided by subclasses, at the cost of some automation for the class creator.+-- Impl is most valuable when instantiating deep (or even undecidably recursive) typeclass hierarchies for multiple new datatypes, which is most common in client code.+module Impl (+ -- * The core Impl class+ Impl(..),Method(..)+ ,Symbol -- | Reexported from @base@+ ,NamedMethods, NamedExpQ+ ,type (:->)+ -- * Utilities for Named arguments+ -- | @impl@ uses "Named" arguments, which work best with @OverloadedLabels@+ ,type (:!) -- | A required named argument.+ --+ -- >>> #foo 'a' :: "foo" :! Char+ ,type (:?) -- | An optional named argument+ --+ -- >>> #foo 'b' :: "foo" :? Char+ ,($$)+ ,defaults -- | A special 'Param' to fill in the remaining 'Optional' arguments with 'Nothing'+ --+ -- @foo :: ("bar" :! String) -> ("baz" :? Char) -> ("quox" :? Int) -> IO ()@+ --+ -- >>> foo $$ #bar "Hello" $$ defaults :: IO ()+ ,arg {- |++'arg' unwraps a named parameter with the specified name. One way to use it is+to match on arguments with @-XViewPatterns@:++@+fn (arg \#t -> t) (arg \#f -> f) = ...+@++This way, the names of parameters can be inferred from the patterns: no type+signature for @fn@ is required. In case a type signature for @fn@ is+provided, the parameters must come in the same order:++@+fn :: "t" :! Integer -> "f" :! Integer -> ...+fn (arg \#t -> t) (arg \#f -> f) = ... -- ok+fn (arg \#f -> f) (arg \#t -> t) = ... -- does not typecheck+@++-}+ , arg'+ ,Param -- | A parameter passable as a named argument. Used implicitly by '($$)' with @OverloadedLabels+ ,NamedF(Arg,Arg') -- | A named argument that could be required or optional depending on the @f@ parameter++ -- * TH reexports and utilities+ ,methodsFor, type (++), TypeQ, DecsQ ) where-import Language.Haskell.TH-import Named hiding (Name)+import Language.Haskell.TH hiding (Name)+import Named+import Named.Internal (Param,NamedF(ArgF)) import GHC.TypeLits (Symbol) import Impl.Utils+ +-- | Typeclasses implementing Impl can build declaratios for their entire superclass heirarchy+-- from a collection of required or optional named methods, allowing potentially complex logic for defaulting.+-- See the @example@ internal library for how to implement instances of 'Impl'. class Impl c where- type Methods c :: [Symbol]+ type Methods c :: [Method Symbol] -- | Instantiate the implementing class along with all its superclasses -- Ex: -- -- > impl @Monad [t|[]|]- -- > ! #return [|\x -> [x]|]- -- > ! #bind [|flip concatMap|]+ -- > $$ #return [|\x -> [x]|]+ -- > $$ #bind [|flip concatMap|] impl :: TypeQ -> NamedMethods c :-> DecsQ --- | +-- | >>> :kind! NamedExpQ '[Required "foo", Optional "bar"]+-- = '["foo" :! ExpQ,"bar" :? ExpQ] type family NamedExpQ ss where NamedExpQ '[] = '[]- NamedExpQ (s ': ss) = (s :! ExpQ) ': NamedExpQ ss+ NamedExpQ (Required s ': ss) = (s :! ExpQ) ': NamedExpQ ss+ NamedExpQ (Optional s ': ss) = (s :? ExpQ) ': NamedExpQ ss -- | "Named" TH 'Exp's for the class method implementations type NamedMethods c = NamedExpQ (Methods c)++arg' :: Name name -> a -> (name :? a) -> a+{- | A variation of 'arg' for optional arguments. Requires a default value to handle+the case when the optional argument was omitted:++@ fn (arg' \#answer 42 -> ans) = ... @++In case you want to get a value wrapped in 'Maybe' instead, 'Arg'' -}+arg' = argDef++pattern Arg' :: Maybe a -> name :? a+-- | Construct or match an optional named argument+pattern Arg' a' = ArgF a'++($$) :: WithParam p fn fn' => fn -> Param p -> fn'+{- | Pass a named (optional or required) argument to a function in any order.++ @foo :: ("bar" :! String) -> ("baz" :? Char) -> IO ()@++ >>> foo $$ #baz 'a' :: ("bar" :! String) -> IO ()+-}+($$) = (!)
Impl/Utils.hs view
@@ -1,8 +1,11 @@ module Impl.Utils where import Language.Haskell.TH+import qualified Data.Map as Map -typeList :: [Type] -> Type-typeList = foldr (\t -> AppT (AppT PromotedConsT t)) PromotedNilT+-- | Type level list append+type family (as :: [x]) ++ (bs :: [x]) :: [x] where+ '[] ++ bs = bs+ (a ': as) ++ bs = a ': (as ++ bs) -- | Converts a variable number of arguments into curried form. Ex: --@@ -13,14 +16,30 @@ (a ': as) :-> r = a -> as :-> r '[] :-> r = r +data Method a = Required a | Optional a+ methodsFor :: Name -- ^ Typeclass name- -> TypeQ -- ^ @ :: [Symbol]@- --- | Retrieve the method names of a typeclass as a typelevel list of @Symbol@s.--- A good default for instantiating the 'Methods' type,--- which is robust against changing method names-methodsFor n = do- ClassI (ClassD _ _ _ _ (map sigSym -> ss)) _ <- reify n- return (typeList ss )- where sigSym (SigD (nameBase -> s) _) = LitT (StrTyLit s)+ -> TypeQ -- ^ @ :: [`Method` `Symbol`]@ +{- | Retrieve the method names of a typeclass as a typelevel list of @Method Symbol@s.+ A good default for instantiating the 'Methods' type,+ which is robust against changing method names. -}+{-# DEPRECATED methodsFor "`reify` doesn't currently allow introspecting default definitions,\+ \ so they are always `Required`" #-}+methodsFor n = do+ ClassI (ClassD _ _ _ _ decs) _ <- reify n+ let methods = Map.elems $ foldr addDecl Map.empty decs+ return (typeList methods)+ where+ mkOptional = \case Required a -> Optional a; Optional a -> Optional a+ addDecl = \case+ SigD n _ty -> Map.insert n+ $ Required (LitT (StrTyLit (nameBase n)))+ ValD (VarP n) _ _ -> Map.adjust mkOptional n+ FunD n _ -> Map.adjust mkOptional n+ typeList :: [Method Type] -> Type+ typeList = foldr (cons . methodType) PromotedNilT where+ cons t = AppT (AppT PromotedConsT t)+ methodType = \case+ Required t -> ConT 'Required `AppT` t+ Optional t -> ConT 'Optional `AppT` t
example/Monad/Foo.hs view
@@ -4,5 +4,7 @@ data Foo a = Foo a impl @Monad [t|Foo|]- ! #return [|Foo|]- ! #bind [|\(Foo a) f -> f a|]+ $$ #return [|Foo|]+ $$ #bind [|\(Foo a) f -> f a|]+ $$ #fmap [|\f (Foo a) -> Foo (f a)|]+ $$ defaults
example/Monad/Impl.hs view
@@ -1,15 +1,41 @@+{-# language UndecidableInstances #-} -- Only needed for ++ idiom module Monad.Impl (module X) where+import Data.Maybe import Impl as X+import Control.Monad.Fail+import GHC.Err +instance Impl Functor where+ type Methods Functor = [Required "fmap", Optional "constMap"]+ impl f (arg #fmap -> fmap) (Arg' constMap') = case constMap' of+ Nothing -> [d|instance Functor $f where fmap = $fmap|]+ Just constMap -> [d|instance Functor $f where fmap = $fmap;(<$) = $constMap|]++instance Impl Applicative where+ type Methods Applicative = '[Required "pure", Required "ap"] ++ Methods Functor+ impl f (arg #pure -> pure) (arg #ap -> ap) fmap constMap+ = concat <$> sequence+ [impl @Functor f fmap constMap+ ,[d|instance Applicative $f where+ pure = $pure+ (<*>) = $ap |]+ ]+ instance Impl Monad where- type Methods Monad = '["return","bind"]- impl m (Arg return) (Arg bind) = [d|- instance Monad $m where- return = $return- (>>=) = $bind- instance Applicative $m where - pure = $return- mf <*> ma = $bind mf $ \f -> $bind ma $ \a -> $return (f a)- instance Functor $m where- fmap f ma = $bind ma $ \a -> $return (f a)- |]+ type Methods Monad = '[Required "return",Required "bind", Optional "fail", Optional "fmap", Optional "constMap"]+ impl m (arg #return -> return)+ (arg #bind -> bind)+ (arg' #fail [|errorWithoutStackTrace|] -> fail)+ (Arg' fmap')+ constMap'+ = concat <$> sequence+ [impl @Applicative m+ $$ #pure return+ $$ #ap [|\mf ma -> $bind mf $ \f -> $bind ma $ \a -> $return (f a) |]+ $$ #fmap (fromMaybe [|\f ma -> $bind ma $ \a -> $return (f a)|] fmap')+ $ constMap'+ ,[d|instance Monad $m where+ return = $return+ (>>=) = $bind+ fail = $fail |]+ ]
impl.cabal view
@@ -1,9 +1,10 @@ cabal-version: 2.2 name: impl homepage: https://github.com/exordium/impl#readme-version: 0.1.0.0+version: 0.2.0.0 category: Development, Template Haskell synopsis: Framework for defaulting superclasses+description: This small but extensible framework facilitates defining complex defaulting rules that are not handled by DefaultSignatures, and reducing the overhead of giving instances to new datatypes by generating superclasses. One reason we might want this is when a superclass wants to be given a default by two different subclasses (ex: Bifunctor and Profunctor both could generate Functor instances). See the example internal library for how to implement instances of Impl. Impl is most valuable when instantiating deep (or even undecidably recursive) typeclass hierarchies for multiple new datatypes, which is most common in client code. stability: cursed bug-reports: https://github.com/exordium/impl/issues author: Dai@@ -17,7 +18,7 @@ common x default-language: Haskell2010- default-extensions: TypeFamilies, ViewPatterns, DataKinds, TypeOperators, AllowAmbiguousTypes, TypeApplications, OverloadedLabels, TemplateHaskell, PolyKinds+ default-extensions: TypeFamilies, ViewPatterns, DataKinds, TypeOperators, AllowAmbiguousTypes, TypeApplications, OverloadedLabels, TemplateHaskell, PolyKinds, PatternSynonyms, LambdaCase build-depends: base ^>= 4.12.0.0 library@@ -26,6 +27,7 @@ other-modules: Impl.Utils build-depends: template-haskell ^>= 2.14.0.0 , named ^>= 0.2.0.0+ , containers ^>= 0.6.0.1 Flag DumpExample Description: Show generated TH in example