dep-t-0.6.8.0: Dependency injection for records-of-functions.
Safe HaskellSafe-Inferred
LanguageHaskell2010

Control.Monad.Dep

Description

This module provides DepT, a monad transformer similar to ReaderT.

The difference with ReaderT is that DepT takes an enviroment whose type is parameterized by DepT itself.

Synopsis

Motivation

Dependency injection.

To perform dependency injection in Haskell, a common solution is to build a record of functions and pass it to the program logic using some variant of ReaderT.

To avoid becoming tied to a concrete reader environment, let's define some auxiliary typeclasses that extract functions from a generic environment:

type HasLogger :: (Type -> Type) -> Type -> Constraint
class HasLogger d e | e -> d where
  logger :: e -> String -> d ()

type HasRepository :: (Type -> Type) -> Type -> Constraint
class HasRepository d e | e -> d where
  repository :: e -> Int -> d ()

We see that the type e of the environment determines the monad d on which the effects take place.

Here's a monomorphic environment record with functions that have effects in IO:

type EnvIO :: Type
data EnvIO = EnvIO
  { _loggerIO :: String -> IO (),
    _repositoryIO :: Int -> IO ()
  }

instance HasLogger IO EnvIO where
  logger = _loggerIO

instance HasRepository IO EnvIO where
  repository = _repositoryIO

Record-of-functions-in-IO is a simple technique which works well in many situations. There are even specialized libraries that support it.

Here's a function which can get its dependencies from the monomorphic environment:

mkControllerIO :: (HasLogger IO e, HasRepository IO e) => Int -> ReaderT e IO String
mkControllerIO x = do
  e <- ask
  liftIO $ logger e "I'm going to insert in the db!"
  liftIO $ repository e x
  return "view"

That's all and well, but there are two issues that bug me:

  • We might want to write code that is innocent of IO and polymorphic over the monad, to ensure that the program logic can't do some unexpected missile launch, or to allow testing our app in a "pure" way.
  • What if the repository function needs access to the logger, too? The repository lives in the environment record, but isn't aware of it. That means it can't use the HasLogger typeclass for easy and convenient dependency injection. Why privilege the controller in such a way?

In a sufficiently complex app, the diverse functions that comprise it will be organized in a big DAG of dependencies. And it would be nice if all the functions taking part in dependency injection were treated uniformly; if all of them had access to (some view of) the environment record.

To tackle these issues, we begin by giving the controller a more general signature:

mkControllerIO :: (HasLogger IO e, HasRepository IO e, MonadIO m, MonadReader e m) => Int -> m String

Now the function can work in other reader-like monads besides ReaderT.

Let's go one step further, and abstract away the IO, so that functions in the record can have effects in other monads:

mkController :: (HasLogger d e, HasRepository d e, LiftDep d m, MonadReader e m) => Int -> m String
mkController x = do
  e <- ask
  liftD $ logger e "I'm going to insert in the db!"
  liftD $ repository e x
  return "view"

Now both the signature and the implementation have changed:

  • There's a new type variable d, the monad in which functions taken from the environment e have their effects.
  • MonadIO has been replaced by LiftDep from Class, a constraint that says we can lift d effects into m (though it could still make sense to require `MonadIO m` for effects not originating in the environment).
  • Uses of liftIO have been replaced by liftD.

If all those constraints prove annoying to write, there's a convenient shorthand using the MonadDep type family:

MonadDep [HasLogger, HasRepository] d e m => Int -> m String

The new, more polymorphic mkController function can replace the original mkControllerIO:

mkControllerIO' :: (HasLogger IO e, HasRepository IO e) => Int -> ReaderT e IO String
mkControllerIO' = mkController

Now let's focus on the environment record. We'll parameterize its type by a monad:

type Env :: (Type -> Type) -> Type
data Env m = Env
  { _logger :: String -> m (),
    _repository :: Int -> m (),
    _controller :: Int -> m String
  }

instance HasLogger m (Env m) where
  logger = _logger

instance HasRepository m (Env m) where
  repository = _repository

Notice that the controller function is now part of the environment. No favorites here!

The following implementation of the logger function has no dependencies besides MonadIO:

mkStdoutLogger :: MonadIO m => String -> m ()
mkStdoutLogger msg = liftIO (putStrLn msg)

But look at this implementation of the repository function. It gets hold of the logger through HasLogger, just as the controller did:

mkStdoutRepository :: (MonadDep '[HasLogger] d e m, MonadIO m) => Int -> m ()
mkStdoutRepository entity = do
  e <- ask
  liftD $ logger e "I'm going to write the entity!"
  liftIO $ print entity

It's about time we choose a concrete monad and assemble an environment record:

envIO :: Env (DepT Env IO)
envIO =
  let _logger = mkStdoutLogger
      _repository = mkStdoutRepository
      _controller = mkController
   in Env {_logger,  _repository, _controller}

Not very complicated, except... what is that weird `DepT Env IO` doing there in the signature?

Well, that's the whole reason this library exists. For dependency injection to work for *all* functions, Env needs to be parameterized with a monad that provides that same Env environment. And trying to use a ReaderT (Env something) IO to parameterize Env won't fly; you'll get weird "infinite type" kind of errors. So I created the DepT newtype over ReaderT to mollify the compiler.

DepT has MonadReader and LiftDep instances, so the effects of mkController can take place on it.

To invoke the controller from the environment, we can do something like

runDepT (do e <- ask; _controller e 7) envIO 

or

(do e <- ask; _controller e 7) runDepT envIO 

The companion package dep-t-advice has some helper functions for running DepT computations.

Caveats

The structure of the DepT type might be prone to trigger a known infelicity of the GHC simplifier.

The DepT transformer

newtype DepT (e_ :: (Type -> Type) -> Type) (m :: Type -> Type) (r :: Type) Source #

A monad transformer which adds a read-only environment to the given monad. The environment type must be parameterized with the transformer stack.

The return function ignores the environment, while >>= passes the inherited environment to both subcomputations.

Constructors

DepT (ReaderT (e_ (DepT e_ m)) m r) 

Instances

Instances details
MonadError e m => MonadError e (DepT e_ m) Source # 
Instance details

Defined in Control.Monad.Dep

Methods

throwError :: e -> DepT e_ m a #

catchError :: DepT e_ m a -> (e -> DepT e_ m a) -> DepT e_ m a #

MonadState s m => MonadState s (DepT e_ m) Source # 
Instance details

Defined in Control.Monad.Dep

Methods

get :: DepT e_ m s #

put :: s -> DepT e_ m () #

state :: (s -> (a, s)) -> DepT e_ m a #

MonadWriter w m => MonadWriter w (DepT e_ m) Source # 
Instance details

Defined in Control.Monad.Dep

Methods

writer :: (a, w) -> DepT e_ m a #

tell :: w -> DepT e_ m () #

listen :: DepT e_ m a -> DepT e_ m (a, w) #

pass :: DepT e_ m (a, w -> w) -> DepT e_ m a #

MonadTrans (DepT e_) Source # 
Instance details

Defined in Control.Monad.Dep

Methods

lift :: Monad m => m a -> DepT e_ m a #

Monad m => MonadReader (e_ (DepT e_ m)) (DepT e_ m) Source # 
Instance details

Defined in Control.Monad.Dep

Methods

ask :: DepT e_ m (e_ (DepT e_ m)) #

local :: (e_ (DepT e_ m) -> e_ (DepT e_ m)) -> DepT e_ m a -> DepT e_ m a #

reader :: (e_ (DepT e_ m) -> a) -> DepT e_ m a #

MonadFail m => MonadFail (DepT e_ m) Source # 
Instance details

Defined in Control.Monad.Dep

Methods

fail :: String -> DepT e_ m a #

MonadFix m => MonadFix (DepT e_ m) Source # 
Instance details

Defined in Control.Monad.Dep

Methods

mfix :: (a -> DepT e_ m a) -> DepT e_ m a #

MonadIO m => MonadIO (DepT e_ m) Source # 
Instance details

Defined in Control.Monad.Dep

Methods

liftIO :: IO a -> DepT e_ m a #

MonadZip m => MonadZip (DepT e_ m) Source # 
Instance details

Defined in Control.Monad.Dep

Methods

mzip :: DepT e_ m a -> DepT e_ m b -> DepT e_ m (a, b) #

mzipWith :: (a -> b -> c) -> DepT e_ m a -> DepT e_ m b -> DepT e_ m c #

munzip :: DepT e_ m (a, b) -> (DepT e_ m a, DepT e_ m b) #

Alternative m => Alternative (DepT e_ m) Source # 
Instance details

Defined in Control.Monad.Dep

Methods

empty :: DepT e_ m a #

(<|>) :: DepT e_ m a -> DepT e_ m a -> DepT e_ m a #

some :: DepT e_ m a -> DepT e_ m [a] #

many :: DepT e_ m a -> DepT e_ m [a] #

Applicative m => Applicative (DepT e_ m) Source # 
Instance details

Defined in Control.Monad.Dep

Methods

pure :: a -> DepT e_ m a #

(<*>) :: DepT e_ m (a -> b) -> DepT e_ m a -> DepT e_ m b #

liftA2 :: (a -> b -> c) -> DepT e_ m a -> DepT e_ m b -> DepT e_ m c #

(*>) :: DepT e_ m a -> DepT e_ m b -> DepT e_ m b #

(<*) :: DepT e_ m a -> DepT e_ m b -> DepT e_ m a #

Functor m => Functor (DepT e_ m) Source # 
Instance details

Defined in Control.Monad.Dep

Methods

fmap :: (a -> b) -> DepT e_ m a -> DepT e_ m b #

(<$) :: a -> DepT e_ m b -> DepT e_ m a #

Monad m => Monad (DepT e_ m) Source # 
Instance details

Defined in Control.Monad.Dep

Methods

(>>=) :: DepT e_ m a -> (a -> DepT e_ m b) -> DepT e_ m b #

(>>) :: DepT e_ m a -> DepT e_ m b -> DepT e_ m b #

return :: a -> DepT e_ m a #

MonadPlus m => MonadPlus (DepT e_ m) Source # 
Instance details

Defined in Control.Monad.Dep

Methods

mzero :: DepT e_ m a #

mplus :: DepT e_ m a -> DepT e_ m a -> DepT e_ m a #

MonadCont m => MonadCont (DepT e_ m) Source # 
Instance details

Defined in Control.Monad.Dep

Methods

callCC :: ((a -> DepT e_ m b) -> DepT e_ m a) -> DepT e_ m a #

MonadUnliftIO m => MonadUnliftIO (DepT e_ m) Source # 
Instance details

Defined in Control.Monad.Dep

Methods

withRunInIO :: ((forall a. DepT e_ m a -> IO a) -> IO b) -> DepT e_ m b #

Monad m => LiftDep (DepT e_ m) (DepT e_ m) Source #

DepT can be d-lifted to itself.

Instance details

Defined in Control.Monad.Dep

Methods

liftD :: DepT e_ m x -> DepT e_ m x Source #

(Monad m, Coercible newtyped (e_ (DepT e_ m))) => LiftDep (DepT e_ m) (ReaderT newtyped m) Source #

DepT can be d-lifted to a ReaderT in which the environment record containing further DepT actions has been hidden behind a newtype.

This can be useful to "deceive" a function into using an environment possessing different instances than the instances seen by the function's dependencies.

Instance details

Defined in Control.Monad.Dep

Methods

liftD :: DepT e_ m x -> ReaderT newtyped m x Source #

runDepT :: DepT e_ m r -> e_ (DepT e_ m) -> m r Source #

Runs a DepT action in an environment.

>>> runDepT (pure "foo") NilEnv
"foo"

For more sophisticated invocation functions, see runFinalDepT and runFromEnv from dep-t-advice.

toReaderT :: DepT e_ m r -> ReaderT (e_ (DepT e_ m)) m r Source #

withDepT Source #

Arguments

:: forall small big m a. Monad m 
=> (forall p q. (forall x. p x -> q x) -> small p -> small q)

rank-2 map function

-> (forall t. big t -> small t)

get a small environment from a big one

-> DepT small m a 
-> DepT big m a 

Analogous to withReaderT.

Changes the environment of a DepT, for example making the DepT work in a "bigger" environment than the one in which was defined initially.

The scary first parameter is a function that, given a natural transformation of monads, changes the monad parameter of the environment record. This function can be defined manually for each environment record, or it can be generated using TH from the rank2classes package.

zoomEnv Source #

Arguments

:: forall small big m a. Monad m 
=> (forall p q. (forall x. p x -> q x) -> small p -> small q)

rank-2 map function

-> (forall t. big t -> small t)

get a small environment from a big one

-> small (DepT small m) 
-> small (DepT big m) 

Makes the functions inside a small environment require a bigger environment.

The scary first parameter is a function that, given a natural transformation of monads, changes the monad parameter of the environment record. This function can be defined manually for each environment record, or it can be generated using TH from the rank2classes package.

zoomEnv can be useful if we are encasing some preexisting small environment as a field of a big environment, in order to make the types match:

>>> :{
  type Env :: (Type -> Type) -> Type
  data Env m = Env
    { _logger :: String -> m (),
      _repository :: Int -> m (),
      _controller :: Int -> m String
    }
  $(Rank2.TH.deriveFunctor ''Env)
  env :: Env (DepT Env IO)
  env = Env 
    { _logger = \_ -> pure (), 
      _repository = \_ -> pure (), 
      _controller = \_ -> pure "foo" 
    }
  type BiggerEnv :: (Type -> Type) -> Type
  data BiggerEnv m = BiggerEnv
    { _inner :: Env m,
      _extra :: Int -> m Int
    }
  biggerEnv :: BiggerEnv (DepT BiggerEnv IO)
  biggerEnv = BiggerEnv 
    { _inner = zoomEnv (Rank2.<$>) _inner env, 
      _extra = pure
    }
:}

However, this is only needed when the monad of the smaller environment is already "fixed" before inserting it in the bigger one—which I expect to be an infrequent case. When the concrete monad is selected after nesting the environments, zoomEnv shouldn't be necessary.

The simplest environment

data NilEnv m Source #

An empty environment that carries no functions, analogous to () for ReaderT.

Constructors

NilEnv 

The next simplest environment

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.

newtype Constant a (b :: k) #

Constant functor.

Constructors

Constant 

Fields

Instances

Instances details
Bifoldable (Constant :: Type -> TYPE LiftedRep -> Type) 
Instance details

Defined in Data.Functor.Constant

Methods

bifold :: Monoid m => Constant m m -> m #

bifoldMap :: Monoid m => (a -> m) -> (b -> m) -> Constant a b -> m #

bifoldr :: (a -> c -> c) -> (b -> c -> c) -> c -> Constant a b -> c #

bifoldl :: (c -> a -> c) -> (c -> b -> c) -> c -> Constant a b -> c #

Bifunctor (Constant :: Type -> Type -> Type) 
Instance details

Defined in Data.Functor.Constant

Methods

bimap :: (a -> b) -> (c -> d) -> Constant a c -> Constant b d #

first :: (a -> b) -> Constant a c -> Constant b c #

second :: (b -> c) -> Constant a b -> Constant a c #

Bitraversable (Constant :: Type -> Type -> Type) 
Instance details

Defined in Data.Functor.Constant

Methods

bitraverse :: Applicative f => (a -> f c) -> (b -> f d) -> Constant a b -> f (Constant c d) #

Eq2 (Constant :: Type -> Type -> Type) 
Instance details

Defined in Data.Functor.Constant

Methods

liftEq2 :: (a -> b -> Bool) -> (c -> d -> Bool) -> Constant a c -> Constant b d -> Bool #

Ord2 (Constant :: Type -> Type -> Type) 
Instance details

Defined in Data.Functor.Constant

Methods

liftCompare2 :: (a -> b -> Ordering) -> (c -> d -> Ordering) -> Constant a c -> Constant b d -> Ordering #

Read2 (Constant :: Type -> Type -> Type) 
Instance details

Defined in Data.Functor.Constant

Methods

liftReadsPrec2 :: (Int -> ReadS a) -> ReadS [a] -> (Int -> ReadS b) -> ReadS [b] -> Int -> ReadS (Constant a b) #

liftReadList2 :: (Int -> ReadS a) -> ReadS [a] -> (Int -> ReadS b) -> ReadS [b] -> ReadS [Constant a b] #

liftReadPrec2 :: ReadPrec a -> ReadPrec [a] -> ReadPrec b -> ReadPrec [b] -> ReadPrec (Constant a b) #

liftReadListPrec2 :: ReadPrec a -> ReadPrec [a] -> ReadPrec b -> ReadPrec [b] -> ReadPrec [Constant a b] #

Show2 (Constant :: Type -> TYPE LiftedRep -> Type) 
Instance details

Defined in Data.Functor.Constant

Methods

liftShowsPrec2 :: (Int -> a -> ShowS) -> ([a] -> ShowS) -> (Int -> b -> ShowS) -> ([b] -> ShowS) -> Int -> Constant a b -> ShowS #

liftShowList2 :: (Int -> a -> ShowS) -> ([a] -> ShowS) -> (Int -> b -> ShowS) -> ([b] -> ShowS) -> [Constant a b] -> ShowS #

Foldable (Constant a :: TYPE LiftedRep -> Type) 
Instance details

Defined in Data.Functor.Constant

Methods

fold :: Monoid m => Constant a m -> m #

foldMap :: Monoid m => (a0 -> m) -> Constant a a0 -> m #

foldMap' :: Monoid m => (a0 -> m) -> Constant a a0 -> m #

foldr :: (a0 -> b -> b) -> b -> Constant a a0 -> b #

foldr' :: (a0 -> b -> b) -> b -> Constant a a0 -> b #

foldl :: (b -> a0 -> b) -> b -> Constant a a0 -> b #

foldl' :: (b -> a0 -> b) -> b -> Constant a a0 -> b #

foldr1 :: (a0 -> a0 -> a0) -> Constant a a0 -> a0 #

foldl1 :: (a0 -> a0 -> a0) -> Constant a a0 -> a0 #

toList :: Constant a a0 -> [a0] #

null :: Constant a a0 -> Bool #

length :: Constant a a0 -> Int #

elem :: Eq a0 => a0 -> Constant a a0 -> Bool #

maximum :: Ord a0 => Constant a a0 -> a0 #

minimum :: Ord a0 => Constant a a0 -> a0 #

sum :: Num a0 => Constant a a0 -> a0 #

product :: Num a0 => Constant a a0 -> a0 #

Eq a => Eq1 (Constant a :: Type -> Type) 
Instance details

Defined in Data.Functor.Constant

Methods

liftEq :: (a0 -> b -> Bool) -> Constant a a0 -> Constant a b -> Bool #

Ord a => Ord1 (Constant a :: Type -> Type) 
Instance details

Defined in Data.Functor.Constant

Methods

liftCompare :: (a0 -> b -> Ordering) -> Constant a a0 -> Constant a b -> Ordering #

Read a => Read1 (Constant a :: Type -> Type) 
Instance details

Defined in Data.Functor.Constant

Methods

liftReadsPrec :: (Int -> ReadS a0) -> ReadS [a0] -> Int -> ReadS (Constant a a0) #

liftReadList :: (Int -> ReadS a0) -> ReadS [a0] -> ReadS [Constant a a0] #

liftReadPrec :: ReadPrec a0 -> ReadPrec [a0] -> ReadPrec (Constant a a0) #

liftReadListPrec :: ReadPrec a0 -> ReadPrec [a0] -> ReadPrec [Constant a a0] #

Show a => Show1 (Constant a :: TYPE LiftedRep -> Type) 
Instance details

Defined in Data.Functor.Constant

Methods

liftShowsPrec :: (Int -> a0 -> ShowS) -> ([a0] -> ShowS) -> Int -> Constant a a0 -> ShowS #

liftShowList :: (Int -> a0 -> ShowS) -> ([a0] -> ShowS) -> [Constant a a0] -> ShowS #

Contravariant (Constant a :: Type -> Type) 
Instance details

Defined in Data.Functor.Constant

Methods

contramap :: (a' -> a0) -> Constant a a0 -> Constant a a' #

(>$) :: b -> Constant a b -> Constant a a0 #

Traversable (Constant a :: Type -> Type) 
Instance details

Defined in Data.Functor.Constant

Methods

traverse :: Applicative f => (a0 -> f b) -> Constant a a0 -> f (Constant a b) #

sequenceA :: Applicative f => Constant a (f a0) -> f (Constant a a0) #

mapM :: Monad m => (a0 -> m b) -> Constant a a0 -> m (Constant a b) #

sequence :: Monad m => Constant a (m a0) -> m (Constant a a0) #

Monoid a => Applicative (Constant a :: Type -> Type) 
Instance details

Defined in Data.Functor.Constant

Methods

pure :: a0 -> Constant a a0 #

(<*>) :: Constant a (a0 -> b) -> Constant a a0 -> Constant a b #

liftA2 :: (a0 -> b -> c) -> Constant a a0 -> Constant a b -> Constant a c #

(*>) :: Constant a a0 -> Constant a b -> Constant a b #

(<*) :: Constant a a0 -> Constant a b -> Constant a a0 #

Functor (Constant a :: Type -> Type) 
Instance details

Defined in Data.Functor.Constant

Methods

fmap :: (a0 -> b) -> Constant a a0 -> Constant a b #

(<$) :: a0 -> Constant a b -> Constant a a0 #

Monoid a => Monoid (Constant a b) 
Instance details

Defined in Data.Functor.Constant

Methods

mempty :: Constant a b #

mappend :: Constant a b -> Constant a b -> Constant a b #

mconcat :: [Constant a b] -> Constant a b #

Semigroup a => Semigroup (Constant a b) 
Instance details

Defined in Data.Functor.Constant

Methods

(<>) :: Constant a b -> Constant a b -> Constant a b #

sconcat :: NonEmpty (Constant a b) -> Constant a b #

stimes :: Integral b0 => b0 -> Constant a b -> Constant a b #

Read a => Read (Constant a b) 
Instance details

Defined in Data.Functor.Constant

Show a => Show (Constant a b) 
Instance details

Defined in Data.Functor.Constant

Methods

showsPrec :: Int -> Constant a b -> ShowS #

show :: Constant a b -> String #

showList :: [Constant a b] -> ShowS #

Eq a => Eq (Constant a b) 
Instance details

Defined in Data.Functor.Constant

Methods

(==) :: Constant a b -> Constant a b -> Bool #

(/=) :: Constant a b -> Constant a b -> Bool #

Ord a => Ord (Constant a b) 
Instance details

Defined in Data.Functor.Constant

Methods

compare :: Constant a b -> Constant a b -> Ordering #

(<) :: Constant a b -> Constant a b -> Bool #

(<=) :: Constant a b -> Constant a b -> Bool #

(>) :: Constant a b -> Constant a b -> Bool #

(>=) :: Constant a b -> Constant a b -> Bool #

max :: Constant a b -> Constant a b -> Constant a b #

min :: Constant a b -> Constant a b -> Constant a b #

Re-exports