Safe Haskell | None |
---|---|
Language | Haskell2010 |
This module provides helpers for building dependency injection environments composed of records.
It's not necessary when defining the record components themselves, in that
case Has
should suffice.
>>>
:{
type Logger :: (Type -> Type) -> Type newtype Logger d = Logger { info :: String -> d () } -- data Repository d = Repository { findById :: Int -> d (Maybe String) , putById :: Int -> String -> d () , insert :: String -> d Int } -- data Controller d = Controller { create :: d Int , append :: Int -> String -> d Bool , inspect :: Int -> d (Maybe String) } -- type EnvHKD :: (Type -> Type) -> (Type -> Type) -> Type data EnvHKD h m = EnvHKD { logger :: h (Logger m), repository :: h (Repository m), controller :: h (Controller m) } deriving stock Generic -- deriving anyclass (FieldsFindableByType, DemotableFieldNames, Phased) -- deriving via Autowired (EnvHKD Identity m) instance Autowireable r_ m (EnvHKD Identity m) => Has r_ m (EnvHKD Identity m) :}
The module also provides a monad transformer-less way of performing dependency
injection, by means of fixEnv
.
Synopsis
- class Has r_ (m :: Type -> Type) (env :: Type) | env -> m
- newtype TheDefaultFieldName (env :: Type) = TheDefaultFieldName env
- newtype TheFieldName (name :: Symbol) (env :: Type) = TheFieldName env
- class FieldsFindableByType (env :: Type) where
- type FindFieldByType env (r :: Type) :: Symbol
- newtype Autowired (env :: Type) = Autowired env
- type Autowireable r_ (m :: Type -> Type) (env :: Type) = HasField (FindFieldByType env (r_ m)) env (Identity (r_ m))
- class Phased (env_ :: (Type -> Type) -> (Type -> Type) -> Type) where
- traverseH :: Applicative f => (forall x. h x -> f (g x)) -> env_ h m -> f (env_ g m)
- liftA2H :: (forall x. a x -> f x -> f' x) -> env_ a m -> env_ f m -> env_ f' m
- pullPhase :: forall f g env_ m. (Applicative f, Phased env_) => env_ (Compose f g) m -> f (env_ g m)
- mapPhase :: forall f' f g env_ m. Phased env_ => (forall x. f x -> f' x) -> env_ (Compose f g) m -> env_ (Compose f' g) m
- liftA2Phase :: forall f' a f g env_ m. Phased env_ => (forall x. a x -> f x -> f' x) -> env_ (Compose a g) m -> env_ (Compose f g) m -> env_ (Compose f' g) m
- class DemotableFieldNames env_ where
- demoteFieldNamesH :: (forall x. String -> h String x) -> env_ (h String) m
- demoteFieldNames :: forall env_ m. DemotableFieldNames env_ => env_ (Constant String) m
- mapPhaseWithFieldNames :: forall f' f g env_ m. (Phased env_, DemotableFieldNames env_) => (forall x. String -> f x -> f' x) -> env_ (Compose f g) m -> env_ (Compose f' g) m
- bindPhase :: forall f g a b. Functor f => f a -> (a -> g b) -> Compose f g b
- skipPhase :: forall f g a. Applicative f => g a -> Compose f g a
- fixEnv :: Phased env_ => env_ (Constructor env_ m) m -> env_ Identity m
- type Constructor (env_ :: (Type -> Type) -> (Type -> Type) -> Type) (m :: Type -> Type) = (->) (env_ Identity m) `Compose` Identity
- constructor :: forall r_ env_ m. (env_ Identity m -> r_ m) -> Constructor env_ m (r_ m)
- data InductiveEnv (rs :: [(Type -> Type) -> Type]) (h :: Type -> Type) (m :: Type -> Type) where
- AddDep :: forall r_ m rs h. h (r_ m) -> InductiveEnv rs h m -> InductiveEnv (r_ ': rs) h m
- EmptyEnv :: forall m h. InductiveEnv '[] h m
- addDep :: forall r_ m rs. r_ m -> InductiveEnv rs Identity m -> InductiveEnv (r_ ': rs) Identity m
- emptyEnv :: forall m. InductiveEnv '[] Identity m
- newtype Identity a = Identity {
- runIdentity :: a
- newtype Constant a (b :: k) = Constant {
- getConstant :: a
- newtype Compose (f :: k -> Type) (g :: k1 -> k) (a :: k1) = Compose {
- getCompose :: f (g a)
A general-purpose Has
class Has r_ (m :: Type -> Type) (env :: Type) | env -> m Source #
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.
Instances
(FieldsFindableByType (env_ m), HasField (FindFieldByType (env_ m) (r_ m)) (env_ m) u, Coercible u (r_ m)) => Has r_ m (Autowired (env_ m)) Source # | |
Defined in Control.Monad.Dep.Env | |
(Dep r_, HasField (DefaultFieldName r_) (env_ m) u, Coercible u (r_ m)) => Has r_ m (TheDefaultFieldName (env_ m)) Source # | |
Defined in Control.Monad.Dep.Env dep :: TheDefaultFieldName (env_ m) -> r_ m Source # | |
(HasField name (env_ m) u, Coercible u (r_ m)) => Has r_ m (TheFieldName name (env_ m)) Source # | |
Defined in Control.Monad.Dep.Env dep :: TheFieldName name (env_ m) -> r_ m Source # | |
InductiveEnvFind r_ m rs => Has r_ m (InductiveEnv rs Identity m) Source # | Works by searching on the list of types. |
Defined in Control.Monad.Dep.Env dep :: InductiveEnv rs Identity m -> r_ m Source # |
Helpers for deriving Has
via the default field name
newtype TheDefaultFieldName (env :: Type) Source #
Helper for DerivingVia
HasField
instances.
It expects the component to have as field name the default fieldname
specified by Dep
.
This is the same behavior as the DefaultSignatures
implementation for
Has
, so maybe it doesn't make much sense to use it, except for
explicitness.
Instances
(Dep r_, HasField (DefaultFieldName r_) (env_ m) u, Coercible u (r_ m)) => Has r_ m (TheDefaultFieldName (env_ m)) Source # | |
Defined in Control.Monad.Dep.Env dep :: TheDefaultFieldName (env_ m) -> r_ m Source # |
via arbitrary field name
newtype TheFieldName (name :: Symbol) (env :: Type) Source #
TheFieldName env |
Instances
(HasField name (env_ m) u, Coercible u (r_ m)) => Has r_ m (TheFieldName name (env_ m)) Source # | |
Defined in Control.Monad.Dep.Env dep :: TheFieldName name (env_ m) -> r_ m Source # |
via autowiring
class FieldsFindableByType (env :: Type) Source #
Class for getting the field name from the field's type.
The default implementation of FindFieldByType
requires a Generic
instance, but users can write their own implementations.
type FindFieldByType env (r :: Type) :: Symbol Source #
type FindFieldByType env r = FindFieldByType_ env r
newtype Autowired (env :: Type) Source #
Helper for DerivingVia
HasField
instances.
The fields are identified by their types.
It uses FindFieldByType
under the hood.
BEWARE: for large records with many components, this technique might incur in long compilation times.
Autowired env |
Instances
(FieldsFindableByType (env_ m), HasField (FindFieldByType (env_ m) (r_ m)) (env_ m) u, Coercible u (r_ m)) => Has r_ m (Autowired (env_ m)) Source # | |
Defined in Control.Monad.Dep.Env |
type Autowireable r_ (m :: Type -> Type) (env :: Type) = HasField (FindFieldByType env (r_ m)) env (Identity (r_ m)) Source #
Constraints required when DerivingVia
all possible instances of Has
in
a single definition.
This only works for environments where all the fields come wrapped in Data.Functor.Identity.
Managing phases
class Phased (env_ :: (Type -> Type) -> (Type -> Type) -> Type) where Source #
Class of 2-parameter environments for which the first parameter h
wraps
each field and corresponds to phases in the construction of the environment,
and the second parameter m
is the effect monad used by each component.
h
will typically be a composition of applicative functors, each one
representing a phase. We advance through the phases by "pulling out" the
outermost phase and running it in some way, until we are are left with a
Constructor
phase, which we can remove using fixEnv
.
Phased
resembles FunctorT, TraversableT and ApplicativeT from the barbies library. Phased
instances can be written in terms of them.
Nothing
traverseH :: Applicative f => (forall x. h x -> f (g x)) -> env_ h m -> f (env_ g m) Source #
default traverseH :: (Generic (env_ h m), Generic (env_ g m), GTraverseH h g (Rep (env_ h m)) (Rep (env_ g m)), Applicative f) => (forall x. h x -> f (g x)) -> env_ h m -> f (env_ g m) Source #
liftA2H :: (forall x. a x -> f x -> f' x) -> env_ a m -> env_ f m -> env_ f' m Source #
Used to implement liftA2Phase
, typically you should use that function instead.
Instances
Phased (InductiveEnv rs) Source # | |
Defined in Control.Monad.Dep.Env traverseH :: forall f h g (m :: Type -> Type). Applicative f => (forall x. h x -> f (g x)) -> InductiveEnv rs h m -> f (InductiveEnv rs g m) Source # liftA2H :: forall a f f' (m :: Type -> Type). (forall x. a x -> f x -> f' x) -> InductiveEnv rs a m -> InductiveEnv rs f m -> InductiveEnv rs f' m Source # |
pullPhase :: forall f g env_ m. (Applicative f, Phased env_) => env_ (Compose f g) m -> f (env_ g m) Source #
Take the outermost phase wrapping each component and "pull it outwards", aggregating the phase's applicative effects.
mapPhase :: forall f' f g env_ m. Phased env_ => (forall x. f x -> f' x) -> env_ (Compose f g) m -> env_ (Compose f' g) m Source #
Modify the outermost phase wrapping each component.
liftA2Phase :: forall f' a f g env_ m. Phased env_ => (forall x. a x -> f x -> f' x) -> env_ (Compose a g) m -> env_ (Compose f g) m -> env_ (Compose f' g) m Source #
Combine two environments with a function that works on their outermost phases.
Working with field names
class DemotableFieldNames env_ where Source #
Class of 2-parameter environments for which it's possible to obtain the names of each field as values.
Nothing
demoteFieldNames :: forall env_ m. DemotableFieldNames env_ => env_ (Constant String) m Source #
Bring down the field names of the environment to the term level and store them in the accumulator of Data.Functor.Constant.
mapPhaseWithFieldNames :: forall f' f g env_ m. (Phased env_, DemotableFieldNames env_) => (forall x. String -> f x -> f' x) -> env_ (Compose f g) m -> env_ (Compose f' g) m Source #
Modify the outermost phase wrapping each component, while having access to the field name of the component.
A typical usage is modifying a "parsing the configuration" phase so that each component looks into a different section of the global configuration field.
Constructing phases
Small convenience functions to help build nested compositions of functors.
bindPhase :: forall f g a b. Functor f => f a -> (a -> g b) -> Compose f g b Source #
Use the result of the previous phase to build the next one.
Can be useful infix.
skipPhase :: forall f g a. Applicative f => g a -> Compose f g a Source #
Don't do anything for the current phase, just wrap the next one.
Injecting dependencies by tying the knot
fixEnv :: Phased env_ => env_ (Constructor env_ m) m -> env_ Identity m Source #
This is a method of performing dependency injection that doesn't require Control.Monad.Dep.DepT at all. In fact, it doesn't require the use of any monad transformer!
If we have a environment whose fields are functions that construct each component by searching for its dependencies in a "fully built" version of the environment, we can "tie the knot" to obtain the "fully built" 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 function, ties a whole record of them.
The env_ (Constructor env_ m) m
parameter might be the result of peeling
away successive layers of applicative functor composition using pullPhase
,
until only the wiring phase remains.
type Constructor (env_ :: (Type -> Type) -> (Type -> Type) -> Type) (m :: Type -> Type) = (->) (env_ Identity m) `Compose` Identity Source #
A phase with the effect of "constructing each component by reading its dependencies from a completed environment".
The Constructor
phase for an environment will typically be parameterized
with the environment itself.
constructor :: forall r_ env_ m. (env_ Identity m -> r_ m) -> Constructor env_ m (r_ m) Source #
Turn an environment-consuming function into a Constructor
that can be slotted
into some field of a Phased
environment.
Inductive environment with anonymous fields
data InductiveEnv (rs :: [(Type -> Type) -> Type]) (h :: Type -> Type) (m :: Type -> Type) where Source #
An inductively constructed environment with anonymous fields.
Can be useful for simple tests, and also for converting Has
-based
components into functions that take their dependencies as separate
positional parameters.
makeController :: (Monad m, Has Logger m env, Has Repository m env) => env -> Controller m makeController = undefined makeControllerPositional :: Monad m => Logger m -> Repository m -> Controller m makeControllerPositional a b = makeController $ addDep @Logger a $ addDep @Repository b $ emptyEnv
AddDep :: forall r_ m rs h. h (r_ m) -> InductiveEnv rs h m -> InductiveEnv (r_ ': rs) h m | |
EmptyEnv :: forall m h. InductiveEnv '[] h m |
Instances
InductiveEnvFind r_ m rs => Has r_ m (InductiveEnv rs Identity m) Source # | Works by searching on the list of types. |
Defined in Control.Monad.Dep.Env dep :: InductiveEnv rs Identity m -> r_ m Source # | |
Phased (InductiveEnv rs) Source # | |
Defined in Control.Monad.Dep.Env traverseH :: forall f h g (m :: Type -> Type). Applicative f => (forall x. h x -> f (g x)) -> InductiveEnv rs h m -> f (InductiveEnv rs g m) Source # liftA2H :: forall a f f' (m :: Type -> Type). (forall x. a x -> f x -> f' x) -> InductiveEnv rs a m -> InductiveEnv rs f m -> InductiveEnv rs f' m Source # |
addDep :: forall r_ m rs. r_ m -> InductiveEnv rs Identity m -> InductiveEnv (r_ ': rs) Identity m Source #
emptyEnv :: forall m. InductiveEnv '[] Identity m Source #
Re-exports
Identity functor and monad. (a non-strict monad)
Since: base-4.8.0.0
Identity | |
|
Instances
Constant functor.
Constant | |
|
Instances
newtype Compose (f :: k -> Type) (g :: k1 -> k) (a :: k1) infixr 9 #
Right-to-left composition of functors. The composition of applicative functors is always applicative, but the composition of monads is not always a monad.
Compose infixr 9 | |
|
Instances
Functor f => Generic1 (Compose f g :: k -> Type) | Since: base-4.9.0.0 |
TestEquality f => TestEquality (Compose f g :: k2 -> Type) | The deduction (via generativity) that if Since: base-4.14.0.0 |
Defined in Data.Functor.Compose | |
(Functor f, Functor g) => Functor (Compose f g) | Since: base-4.9.0.0 |
(Applicative f, Applicative g) => Applicative (Compose f g) | Since: base-4.9.0.0 |
Defined in Data.Functor.Compose | |
(Foldable f, Foldable g) => Foldable (Compose f g) | Since: base-4.9.0.0 |
Defined in Data.Functor.Compose fold :: Monoid m => Compose f g m -> m # foldMap :: Monoid m => (a -> m) -> Compose f g a -> m # foldMap' :: Monoid m => (a -> m) -> Compose f g a -> m # foldr :: (a -> b -> b) -> b -> Compose f g a -> b # foldr' :: (a -> b -> b) -> b -> Compose f g a -> b # foldl :: (b -> a -> b) -> b -> Compose f g a -> b # foldl' :: (b -> a -> b) -> b -> Compose f g a -> b # foldr1 :: (a -> a -> a) -> Compose f g a -> a # foldl1 :: (a -> a -> a) -> Compose f g a -> a # toList :: Compose f g a -> [a] # null :: Compose f g a -> Bool # length :: Compose f g a -> Int # elem :: Eq a => a -> Compose f g a -> Bool # maximum :: Ord a => Compose f g a -> a # minimum :: Ord a => Compose f g a -> a # | |
(Traversable f, Traversable g) => Traversable (Compose f g) | Since: base-4.9.0.0 |
Defined in Data.Functor.Compose | |
(Eq1 f, Eq1 g) => Eq1 (Compose f g) | Since: base-4.9.0.0 |
(Ord1 f, Ord1 g) => Ord1 (Compose f g) | Since: base-4.9.0.0 |
Defined in Data.Functor.Compose | |
(Read1 f, Read1 g) => Read1 (Compose f g) | Since: base-4.9.0.0 |
Defined in Data.Functor.Compose liftReadsPrec :: (Int -> ReadS a) -> ReadS [a] -> Int -> ReadS (Compose f g a) # liftReadList :: (Int -> ReadS a) -> ReadS [a] -> ReadS [Compose f g a] # liftReadPrec :: ReadPrec a -> ReadPrec [a] -> ReadPrec (Compose f g a) # liftReadListPrec :: ReadPrec a -> ReadPrec [a] -> ReadPrec [Compose f g a] # | |
(Show1 f, Show1 g) => Show1 (Compose f g) | Since: base-4.9.0.0 |
(Alternative f, Applicative g) => Alternative (Compose f g) | Since: base-4.9.0.0 |
(Eq1 f, Eq1 g, Eq a) => Eq (Compose f g a) | Since: base-4.9.0.0 |
(Typeable a, Typeable f, Typeable g, Typeable k1, Typeable k2, Data (f (g a))) => Data (Compose f g a) | Since: base-4.9.0.0 |
Defined in Data.Functor.Compose gfoldl :: (forall d b. Data d => c (d -> b) -> d -> c b) -> (forall g0. g0 -> c g0) -> Compose f g a -> c (Compose f g a) # gunfold :: (forall b r. Data b => c (b -> r) -> c r) -> (forall r. r -> c r) -> Constr -> c (Compose f g a) # toConstr :: Compose f g a -> Constr # dataTypeOf :: Compose f g a -> DataType # dataCast1 :: Typeable t => (forall d. Data d => c (t d)) -> Maybe (c (Compose f g a)) # dataCast2 :: Typeable t => (forall d e. (Data d, Data e) => c (t d e)) -> Maybe (c (Compose f g a)) # gmapT :: (forall b. Data b => b -> b) -> Compose f g a -> Compose f g a # gmapQl :: (r -> r' -> r) -> r -> (forall d. Data d => d -> r') -> Compose f g a -> r # gmapQr :: forall r r'. (r' -> r -> r) -> r -> (forall d. Data d => d -> r') -> Compose f g a -> r # gmapQ :: (forall d. Data d => d -> u) -> Compose f g a -> [u] # gmapQi :: Int -> (forall d. Data d => d -> u) -> Compose f g a -> u # gmapM :: Monad m => (forall d. Data d => d -> m d) -> Compose f g a -> m (Compose f g a) # gmapMp :: MonadPlus m => (forall d. Data d => d -> m d) -> Compose f g a -> m (Compose f g a) # gmapMo :: MonadPlus m => (forall d. Data d => d -> m d) -> Compose f g a -> m (Compose f g a) # | |
(Ord1 f, Ord1 g, Ord a) => Ord (Compose f g a) | Since: base-4.9.0.0 |
Defined in Data.Functor.Compose compare :: Compose f g a -> Compose f g a -> Ordering # (<) :: Compose f g a -> Compose f g a -> Bool # (<=) :: Compose f g a -> Compose f g a -> Bool # (>) :: Compose f g a -> Compose f g a -> Bool # (>=) :: Compose f g a -> Compose f g a -> Bool # | |
(Read1 f, Read1 g, Read a) => Read (Compose f g a) | Since: base-4.9.0.0 |
(Show1 f, Show1 g, Show a) => Show (Compose f g a) | Since: base-4.9.0.0 |
Generic (Compose f g a) | Since: base-4.9.0.0 |
type Rep1 (Compose f g :: k -> Type) | |
Defined in Data.Functor.Compose | |
type Rep (Compose f g a) | |
Defined in Data.Functor.Compose |