dep-t 0.6.0.0 → 0.6.1.0
raw patch · 7 files changed
+166/−11 lines, 7 filesdep ~mtldep ~transformersdep ~unliftio-corePVP ok
version bump matches the API change (PVP)
Dependency ranges changed: mtl, transformers, unliftio-core
API changes (from Hackage documentation)
+ Control.Monad.Dep: Constant :: a -> Constant a (b :: k)
+ Control.Monad.Dep: [getConstant] :: Constant a (b :: k) -> a
+ Control.Monad.Dep: newtype Constant a (b :: k)
Files
- CHANGELOG.md +4/−0
- dep-t.cabal +1/−1
- lib/Control/Monad/Dep.hs +13/−0
- lib/Dep/Env.hs +81/−6
- lib/Dep/Has.hs +14/−3
- lib/Dep/Tagged.hs +45/−0
- test/doctests.hs +8/−1
CHANGELOG.md view
@@ -1,5 +1,9 @@ # Revision history for dep-t +## 0.6.1.0 + +* Re-export `Data.Functor.Constant` from `Control.Monad.Dep`. https://github.com/danidiaz/dep-t/issues/18 + ## 0.6.0.0 * Added module `Dep.Tagged`.
dep-t.cabal view
@@ -1,7 +1,7 @@ cabal-version: 3.0 name: dep-t -version: 0.6.0.0 +version: 0.6.1.0 synopsis: Dependency injection for records-of-functions. description: Put all your functions in the environment record! Let all your functions read from the environment record! No favorites!
lib/Control/Monad/Dep.hs view
@@ -28,6 +28,9 @@ zoomEnv, -- * The simplest environment NilEnv(NilEnv), + -- * The next simplest environment + -- $constant + Constant(..), -- * Re-exports module Control.Monad.Trans, module Control.Monad.Dep.Class, @@ -50,6 +53,7 @@ import Control.Monad.Dep.Class import Data.Kind (Type) import Data.Coerce +import Data.Functor.Constant -- $setup -- @@ -223,3 +227,12 @@ type NilEnv :: (Type -> Type) -> Type data NilEnv m = NilEnv + +-- $constant +-- +-- 'Constant', which has a phantom type parameter, is a valid environment for +-- 'DepT'. +-- +-- @DepT (Constant e) m@ makes 'DepT' behave similarly to @ReaderT e m@, +-- in that the environment @e@ is independent of the monad. +--
lib/Dep/Env.hs view
@@ -133,9 +133,12 @@ -- >>> :set -XDeriveAnyClass -- >>> :set -XStandaloneDeriving -- >>> :set -XUndecidableInstances +-- >>> :set -XTypeOperators -- >>> import Data.Kind --- >>> import Dep.Has +-- >>> import Data.Function ((&)) +-- >>> import Control.Monad.IO.Class -- >>> import Dep.Env +-- >>> import Dep.Env -- >>> import GHC.Generics (Generic) -- @@ -320,6 +323,18 @@ -- | Take the outermost phase wrapping each component and \"pull it outwards\", -- aggregating the phase's applicative effects. +-- +-- >>> :{ +-- newtype Foo d = Foo {foo :: String -> d ()} deriving Generic +-- makeIOFoo :: MonadIO m => Foo m +-- makeIOFoo = Foo (liftIO . putStrLn) +-- env :: InductiveEnv '[Foo] (IO `Compose` Constructor (InductiveEnv '[Foo] Identity IO)) IO +-- env = EmptyEnv +-- & AddDep @Foo (putStrLn "io phase" `bindPhase` \() -> constructor (\_ -> makeIOFoo)) +-- ioOutside :: IO (InductiveEnv '[Foo] (Constructor (InductiveEnv '[Foo] Identity IO)) IO) +-- ioOutside = pullPhase env +-- :} +-- pullPhase :: forall (f :: Type -> Type) (g :: Type -> Type) (m :: Type -> Type) env_ . (Phased env_, Applicative f, Typeable f, Typeable g, Typeable m) => -- | @@ -331,6 +346,19 @@ pullPhase = traverseH @env_ getCompose -- | Modify the outermost phase wrapping each component. +-- +-- >>> :{ +-- newtype Foo d = Foo {foo :: String -> d ()} deriving Generic +-- makeIOFoo :: MonadIO m => Foo m +-- makeIOFoo = Foo (liftIO . putStrLn) +-- env :: InductiveEnv '[Foo] ((,) Int `Compose` Constructor String) IO +-- env = EmptyEnv +-- & AddDep @Foo ((2,()) `bindPhase` \() -> constructor (\_ -> makeIOFoo)) +-- env' :: InductiveEnv '[Foo] ((,) String `Compose` Constructor String) IO +-- env' = mapPhase (\(n,x) -> (show n,x)) env +-- :} +-- +-- mapPhase :: forall (f :: Type -> Type) (f' :: Type -> Type) (g :: Type -> Type) (m :: Type -> Type) env_ . (Phased env_ , Typeable f, Typeable f', Typeable g, Typeable m) => -- | @@ -488,11 +516,32 @@ -- | Use the result of the previous phase to build the next one. -- -- Can be useful infix. +-- +-- >>> :{ +-- type Phases = IO `Compose` IO `Compose` Identity +-- phased :: Phases Int +-- phased = +-- pure 1 `bindPhase` \i1 -> +-- pure 2 `bindPhase` \i2 -> +-- Identity (i1 + i2) +-- :} +-- +-- bindPhase :: forall f g a b . Functor f => f a -> (a -> g b) -> Compose f g b -- f as first type parameter to help annotate the current phase bindPhase f k = Compose (f <&> k) -- | Don't do anything for the current phase, just wrap the next one. +-- +-- >>> :{ +-- type Phases = IO `Compose` IO `Compose` Identity +-- phased :: Phases Int +-- phased = +-- skipPhase $ +-- skipPhase $ +-- Identity 1 +-- :} +-- skipPhase :: forall f g a . Applicative f => g a -> Compose f g a -- f as first type parameter to help annotate the current phase skipPhase g = Compose (pure g) @@ -521,18 +570,43 @@ -- environment. This works as long as there aren't any circular dependencies -- between components. -- --- Think of it as a version of "Data.Function.fix" that, instead of \"tying\" a single +-- Think of it as a version of 'Data.Function.fix' that, instead of \"tying\" a single -- function, ties a whole record of them. -- --- The @env_ (Constructor env_ m) m@ parameter might be the result of peeling +-- The @env_ (Constructor (env_ Identity m)) m@ parameter might be the result of peeling -- away successive layers of applicative functor composition using 'pullPhase', -- until only the wiring phase remains. -fixEnv :: (Phased env_, Typeable env_, Typeable m) => env_ (Constructor (env_ Identity m) ) m -> env_ Identity m +-- +-- >>> :{ +-- newtype Foo d = Foo {foo :: String -> d ()} deriving Generic +-- newtype Bar d = Bar {bar :: String -> d ()} deriving Generic +-- makeIOFoo :: MonadIO m => Foo m +-- makeIOFoo = Foo (liftIO . putStrLn) +-- makeBar :: Has Foo m env => env -> Bar m +-- makeBar (asCall -> call) = Bar (call foo) +-- env :: InductiveEnv [Bar,Foo] (Constructor (InductiveEnv [Bar,Foo] Identity IO)) IO +-- env = EmptyEnv +-- & AddDep @Foo (constructor (\_ -> makeIOFoo)) +-- & AddDep @Bar (constructor makeBar) +-- envReady :: InductiveEnv [Bar,Foo] Identity IO +-- envReady = fixEnv env +-- :} +-- +-- >>> :{ +-- bar (dep envReady) "this is bar" +-- :} +-- this is bar +-- +fixEnv :: (Phased env_, Typeable env_, Typeable m) => + -- | Environment where each field is wrapped in a 'Constructor' + env_ (Constructor (env_ Identity m)) m -> + -- | Fully constructed environment, ready for use. + env_ Identity m fixEnv env = fix (pullPhase env) -- | An inductively constructed environment with anonymous fields. -- --- Can be useful for simple tests, and also for converting `Has`-based +-- Can be useful for simple tests. Also for converting `Has`-based -- components into functions that take their dependencies as separate -- positional parameters. -- @@ -540,7 +614,8 @@ -- > makeController = undefined -- > makeControllerPositional :: Monad m => Logger m -> Repository m -> Controller m -- > makeControllerPositional a b = makeController $ addDep @Logger a $ addDep @Repository b $ emptyEnv --- +-- > makeController' :: (Monad m, Has Logger m env, Has Repository m env) => env -> Controller m +-- > makeController' env = makeControllerPositional (dep env) (dep env) -- -- data InductiveEnv (rs :: [(Type -> Type) -> Type]) (h :: Type -> Type) (m :: Type -> Type) where
lib/Dep/Has.hs view
@@ -82,7 +82,8 @@ -- module Dep.Has ( -- * A general-purpose Has - Has (..) + Has (..) + , HasAll -- * call helper , asCall -- * Component defaults @@ -97,8 +98,10 @@ -- import Control.Monad.Dep.Class -- | A generic \"Has\" class. When partially applied to a parametrizable --- record-of-functions @r_@, produces a 2-place constraint that can used on its --- own, or with "Control.Monad.Dep.Class". +-- record-of-functions @r_@, produces a 2-place constraint +-- saying that the environment @e@ has the record @r_@ with effect monad @m@. +-- +-- The constraint can used on its own, or with "Control.Monad.Dep.Class". type Has :: ((Type -> Type) -> Type) -> (Type -> Type) -> Type -> Constraint class Has r_ (m :: Type -> Type) (env :: Type) | env -> m where -- | Given an environment @e@, produce a record-of-functions parameterized by the environment's effect monad @m@. @@ -110,6 +113,14 @@ dep :: env -> r_ m default dep :: (Dep r_, HasField (DefaultFieldName r_) env u, Coercible u (r_ m)) => env -> r_ m dep env = coerce . getField @(DefaultFieldName r_) $ env + +-- | When partially applied to a type-level list @rs_@ of parametrizable records-of-functions, +-- produces a 2-place constraint saying that the environment @e@ has all the +-- records @rs_@ with effect monad @m@. +type HasAll :: [(Type -> Type) -> Type] -> (Type -> Type) -> Type -> Constraint +type family HasAll rs_ m e where + HasAll '[] m e = () + HasAll (r_ : rs_) m e = (Has r_ m e, HasAll rs_ m e) -- | Transforms an environment with suitable 'Has' instances into a \"helper\" -- function that looks in the environment for the arguments of other functions.
lib/Dep/Tagged.hs view
@@ -8,6 +8,29 @@ -- -- Similar in purpose to the [Qualifier annotation](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/beans/factory/annotation/Qualifier.html) in Java Spring. -- +-- >>> :{ +-- newtype Foo d = Foo {foo :: String -> d ()} deriving Generic +-- makeIOFoo :: MonadIO m => Foo m +-- makeIOFoo = Foo (liftIO . putStrLn) +-- makeIOFoo' :: MonadIO m => Foo m +-- makeIOFoo' = Foo (\_ -> liftIO $ putStrLn "this is secondary") +-- env :: InductiveEnv '[Foo, Tagged "secondary" Foo] Identity IO +-- env = AddDep @Foo (Identity makeIOFoo) +-- $ AddDep @(Tagged "secondary" Foo) (Identity (tagged makeIOFoo')) +-- $ EmptyEnv +-- :} +-- +-- >>> :{ +-- foo (dep env) "this is foo" +-- :} +-- this is foo +-- +-- >>> :{ +-- foo (untag @"secondary" (dep env)) "this is foo" +-- :} +-- this is secondary +-- +-- -- When using functions from "Dep.SimpleAdvice" (which tend to depend on coercions) with 'Tagged' components, remember to import the newtype's constructor. module Dep.Tagged ( @@ -44,3 +67,25 @@ untag :: Tagged s r_ m -> r_ m untag = unTagged +-- $setup +-- +-- >>> :set -XTypeApplications +-- >>> :set -XMultiParamTypeClasses +-- >>> :set -XImportQualifiedPost +-- >>> :set -XTemplateHaskell +-- >>> :set -XStandaloneKindSignatures +-- >>> :set -XNamedFieldPuns +-- >>> :set -XFunctionalDependencies +-- >>> :set -XFlexibleContexts +-- >>> :set -XDataKinds +-- >>> :set -XBlockArguments +-- >>> :set -XFlexibleInstances +-- >>> :set -XTypeFamilies +-- >>> :set -XDeriveGeneric +-- >>> :set -XViewPatterns +-- >>> import Data.Kind +-- >>> import Control.Monad.IO.Class +-- >>> import GHC.Generics (Generic) +-- >>> import Dep.Has +-- >>> import Dep.Env +--
test/doctests.hs view
@@ -1,5 +1,12 @@ module Main (main) where import Test.DocTest -main = doctest ["-ilib", "lib/Control/Monad/Dep.hs", "lib/Control/Monad/Dep/Class.hs", "lib/Dep/Has.hs", "lib/Dep/Env.hs"] +main = doctest [ + "-ilib", + "lib/Control/Monad/Dep.hs", + "lib/Control/Monad/Dep/Class.hs", + "lib/Dep/Has.hs", + "lib/Dep/Env.hs", + "lib/Dep/Tagged.hs" + ]